From 7e98f686c0683344c12b02fd35d70769ef05aa22 Mon Sep 17 00:00:00 2001 From: upnotes Date: Fri, 10 Aug 2018 15:00:26 +0200 Subject: [PATCH] Decompilation of synchronized blocks generated by the Kotlin compiler --- .../main/extern/IFernflowerPreferences.java | 2 + .../main/rels/MethodProcessorRunnable.java | 5 + .../modules/code/DeadCodeHelper.java | 147 ++++++++++++++++++ .../java/decompiler/SingleClassesTest.java | 1 + .../pkg/TestSynchronizedUnprotected.class | Bin 0 -> 532 bytes .../results/TestSynchronizedUnprotected.dec | 25 +++ 6 files changed, 180 insertions(+) create mode 100644 testData/classes/pkg/TestSynchronizedUnprotected.class create mode 100644 testData/results/TestSynchronizedUnprotected.dec diff --git a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java index 7a965e8..3c1f622 100644 --- a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java +++ b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java @@ -17,6 +17,7 @@ public interface IFernflowerPreferences { String HIDE_DEFAULT_CONSTRUCTOR = "hdc"; String DECOMPILE_GENERIC_SIGNATURES = "dgs"; String NO_EXCEPTIONS_RETURN = "ner"; + String KT_SYNCHRONIZED_MONITOR = "ksm"; String DECOMPILE_ENUM = "den"; String REMOVE_GET_CLASS_NEW = "rgn"; String LITERALS_AS_IS = "lit"; @@ -61,6 +62,7 @@ public interface IFernflowerPreferences { defaults.put(HIDE_DEFAULT_CONSTRUCTOR, "1"); defaults.put(DECOMPILE_GENERIC_SIGNATURES, "0"); defaults.put(NO_EXCEPTIONS_RETURN, "1"); + defaults.put(KT_SYNCHRONIZED_MONITOR, "1"); defaults.put(DECOMPILE_ENUM, "1"); defaults.put(REMOVE_GET_CLASS_NEW, "1"); defaults.put(LITERALS_AS_IS, "0"); diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java index 8217423..cc5417a 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java @@ -88,6 +88,11 @@ public class MethodProcessorRunnable implements Runnable { 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)) { // special case: single return instruction outside of a protected range DeadCodeHelper.incorporateValueReturns(graph); diff --git a/src/org/jetbrains/java/decompiler/modules/code/DeadCodeHelper.java b/src/org/jetbrains/java/decompiler/modules/code/DeadCodeHelper.java index 143dbca..9d28d0e 100644 --- a/src/org/jetbrains/java/decompiler/modules/code/DeadCodeHelper.java +++ b/src/org/jetbrains/java/decompiler/modules/code/DeadCodeHelper.java @@ -4,6 +4,7 @@ package org.jetbrains.java.decompiler.modules.code; 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.code.SimpleInstructionSequence; import org.jetbrains.java.decompiler.code.cfg.BasicBlock; import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; 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 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 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 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) { for (BasicBlock block : graph.getBlocks()) { diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index c8a7cad..6b76492 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -109,6 +109,7 @@ public class SingleClassesTest { @Test public void testMissingConstructorCallBad() { doTest("pkg/TestMissingConstructorCallBad"); } @Test public void testEmptyBlocks() { doTest("pkg/TestEmptyBlocks"); } @Test public void testPrivateEmptyConstructor() { doTest("pkg/TestPrivateEmptyConstructor"); } + @Test public void testSynchronizedUnprotected() { doTest("pkg/TestSynchronizedUnprotected"); } // TODO: fix all below diff --git a/testData/classes/pkg/TestSynchronizedUnprotected.class b/testData/classes/pkg/TestSynchronizedUnprotected.class new file mode 100644 index 0000000000000000000000000000000000000000..2346ca5f54ca7e18a1e9b6c92ef8d7039805dbea GIT binary patch literal 532 zcmaKoOHaZ;6otEy9%4L8E`jH7+!9p^5DL zNiNhwiLZ@KdT;04@7z1RukVjf0M?ODAcoX1(wK~6B!LXF5>pb>0ByHWK+-41-CYDU#p8}6lRLn1CQYhw=c5;+?SSQME1$0sn}cb1y2J}vE68*Cd<&gLM(m~*9sW4k%vlMksV2rUYk!4GpN(R;kF-syTF$y6u_C|=?W0A8z E0i;ZCmH+?% literal 0 HcmV?d00001 diff --git a/testData/results/TestSynchronizedUnprotected.dec b/testData/results/TestSynchronizedUnprotected.dec new file mode 100644 index 0000000..8a789bd --- /dev/null +++ b/testData/results/TestSynchronizedUnprotected.dec @@ -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 +