Roman Shevchenko 6 years ago
commit bc3e79bc94
  1. 4
      src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java
  2. 44
      src/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java
  3. 5
      src/org/jetbrains/java/decompiler/modules/decompiler/decompose/DominatorTreeExceptionFilter.java
  4. 146
      src/org/jetbrains/java/decompiler/modules/decompiler/deobfuscator/ExceptionDeobfuscator.java
  5. 2
      src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java
  6. 2
      test/org/jetbrains/java/decompiler/SingleClassesTest.java
  7. BIN
      testData/classes/pkg/TestFieldSingleAccess.class
  8. BIN
      testData/classes/pkg/TestNamedSuspendFun2Kt$foo2$1.class
  9. BIN
      testData/classes/pkg/TestNamedSuspendFun2Kt.class
  10. 40
      testData/results/TestFieldSingleAccess.dec
  11. 12
      testData/results/TestMethodReferenceSameName.dec
  12. 301
      testData/results/TestNamedSuspendFun2Kt.dec
  13. 67
      testData/src/pkg/TestFieldSingleAccess.jasm
  14. 14
      testData/src/pkg/TestNamedSuspendFun2.kt

@ -107,6 +107,10 @@ public class MethodProcessorRunnable implements Runnable {
if (ExceptionDeobfuscator.hasObfuscatedExceptions(graph)) {
DecompilerContext.getLogger().writeMessage("Heavily obfuscated exception ranges found!", IFernflowerLogger.Severity.WARN);
if(!ExceptionDeobfuscator.handleMultipleEntryExceptionRanges(graph)) {
DecompilerContext.getLogger().writeMessage("Found multiple entry exception ranges which could not be splitted", IFernflowerLogger.Severity.WARN);
}
ExceptionDeobfuscator.insertDummyExceptionHandlerBlocks(graph, cl.getBytecodeVersion());
}
RootStatement root = DomHelper.parseGraph(graph);

@ -178,7 +178,7 @@ public class SimplifyExprentsHelper {
}
// expr++ and expr--
if (isIPPorIMM(current, next)) {
if (isIPPorIMM(current, next) || isIPPorIMM2(current, next)) {
list.remove(index + 1);
res = true;
continue;
@ -458,6 +458,48 @@ public class SimplifyExprentsHelper {
return false;
}
private static boolean isIPPorIMM2(Exprent first, Exprent second) {
if (first.type != Exprent.EXPRENT_ASSIGNMENT || second.type != Exprent.EXPRENT_ASSIGNMENT) {
return false;
}
AssignmentExprent af = (AssignmentExprent)first;
AssignmentExprent as = (AssignmentExprent)second;
if(as.getRight().type != Exprent.EXPRENT_FUNCTION) {
return false;
}
FunctionExprent func = (FunctionExprent)as.getRight();
if(func.getFuncType() != FunctionExprent.FUNCTION_ADD && func.getFuncType() != FunctionExprent.FUNCTION_SUB) {
return false;
}
Exprent econd = func.getLstOperands().get(0);
Exprent econst = func.getLstOperands().get(1);
if(econst.type != Exprent.EXPRENT_CONST && econd.type == Exprent.EXPRENT_CONST && func.getFuncType() == FunctionExprent.FUNCTION_ADD) {
econd = econst;
econst = func.getLstOperands().get(0);
}
if(econst.type == Exprent.EXPRENT_CONST && ((ConstExprent)econst).hasValueOne()) {
if(af.getLeft().equals(econd) && af.getRight().equals(as.getLeft()) && (af.getLeft().getExprentUse() & Exprent.MULTIPLE_USES) != 0) {
int type = func.getFuncType() == FunctionExprent.FUNCTION_ADD ? FunctionExprent.FUNCTION_IPP : FunctionExprent.FUNCTION_IMM;
FunctionExprent ret = new FunctionExprent(type, af.getRight(), func.bytecode);
ret.setImplicitType(VarType.VARTYPE_INT);
af.setRight(ret);
return true;
}
}
return false;
}
private static boolean isMonitorExit(Exprent first) {
if (first.type == Exprent.EXPRENT_MONITOR) {
MonitorExprent expr = (MonitorExprent)first;

@ -134,8 +134,9 @@ public class DominatorTreeExceptionFilter {
exit = childid;
}
else {
// exit = map.containsKey(handler)?-1:mapChild.get(handler); FIXME: Eclipse bug?
exit = map.containsKey(handler) ? -1 : mapChild.get(handler);
// after replacing 'new Integer(-1)' with '-1' Eclipse throws a NullPointerException on the following line
// could be a bug in Eclipse or some obscure specification glitch, FIXME: needs further investigation
exit = map.containsKey(handler) ? new Integer(-1) : mapChild.get(handler);
}
if (exit != null) {

@ -8,6 +8,8 @@ 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;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import org.jetbrains.java.decompiler.modules.decompiler.decompose.GenericDominatorEngine;
import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraph;
import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraphNode;
@ -233,7 +235,7 @@ public class ExceptionDeobfuscator {
if (rangeList.contains(handler)) { // TODO: better removing strategy
List<BasicBlock> lstRemBlocks = getReachableBlocksRestricted(range, engine);
List<BasicBlock> lstRemBlocks = getReachableBlocksRestricted(range.getHandler(), range, engine);
if (lstRemBlocks.size() < rangeList.size() || rangeList.size() == 1) {
for (BasicBlock block : lstRemBlocks) {
@ -249,22 +251,21 @@ public class ExceptionDeobfuscator {
}
}
private static List<BasicBlock> getReachableBlocksRestricted(ExceptionRangeCFG range, GenericDominatorEngine engine) {
private static List<BasicBlock> getReachableBlocksRestricted(BasicBlock start, ExceptionRangeCFG range, GenericDominatorEngine engine) {
List<BasicBlock> lstRes = new ArrayList<>();
LinkedList<BasicBlock> stack = new LinkedList<>();
Set<BasicBlock> setVisited = new HashSet<>();
BasicBlock handler = range.getHandler();
stack.addFirst(handler);
stack.addFirst(start);
while (!stack.isEmpty()) {
BasicBlock block = stack.removeFirst();
setVisited.add(block);
if (range.getProtectedRange().contains(block) && engine.isDominator(block, handler)) {
if (range.getProtectedRange().contains(block) && engine.isDominator(block, start)) {
lstRes.add(block);
List<BasicBlock> lstSuccs = new ArrayList<>(block.getSuccs());
@ -308,4 +309,139 @@ public class ExceptionDeobfuscator {
return false;
}
public static boolean handleMultipleEntryExceptionRanges(ControlFlowGraph graph) {
GenericDominatorEngine engine = new GenericDominatorEngine(new IGraph() {
public List<? extends IGraphNode> getReversePostOrderList() {
return graph.getReversePostOrder();
}
public Set<? extends IGraphNode> getRoots() {
return new HashSet<>(Collections.singletonList(graph.getFirst()));
}
});
engine.initialize();
boolean found = false;
while(true) {
found = false;
boolean splitted = false;
for(ExceptionRangeCFG range : graph.getExceptions()) {
Set<BasicBlock> setEntries = getRangeEntries(range);
if(setEntries.size() > 1) { // multiple-entry protected range
found = true;
if(splitExceptionRange(range, setEntries, graph, engine)) {
splitted = true;
break;
}
}
}
if(!splitted) {
break;
}
}
return !found;
}
private static Set<BasicBlock> getRangeEntries(ExceptionRangeCFG range) {
Set<BasicBlock> setEntries = new HashSet<>();
Set<BasicBlock> setRange= new HashSet<>(range.getProtectedRange());
for(BasicBlock block : range.getProtectedRange()) {
Set<BasicBlock> setPreds = new HashSet<>(block.getPreds());
setPreds.removeAll(setRange);
if (!setPreds.isEmpty()) {
setEntries.add(block);
}
}
return setEntries;
}
private static boolean splitExceptionRange(ExceptionRangeCFG range, Set<BasicBlock> setEntries, ControlFlowGraph graph, GenericDominatorEngine engine) {
for(BasicBlock entry : setEntries) {
List<BasicBlock> lstSubrangeBlocks = getReachableBlocksRestricted(entry, range, engine);
if(!lstSubrangeBlocks.isEmpty() && lstSubrangeBlocks.size() < range.getProtectedRange().size()) {
// add new range
ExceptionRangeCFG subRange = new ExceptionRangeCFG(lstSubrangeBlocks, range.getHandler(), range.getExceptionTypes());
graph.getExceptions().add(subRange);
// shrink the original range
range.getProtectedRange().removeAll(lstSubrangeBlocks);
return true;
} else {
// should not happen
DecompilerContext.getLogger().writeMessage("Inconsistency found while splitting protected range", IFernflowerLogger.Severity.WARN);
}
}
return false;
}
public static void insertDummyExceptionHandlerBlocks(ControlFlowGraph graph, int bytecode_version) {
Map<BasicBlock, Set<ExceptionRangeCFG>> mapRanges = new HashMap<>();
for (ExceptionRangeCFG range : graph.getExceptions()) {
mapRanges.computeIfAbsent(range.getHandler(), k -> new HashSet<>()).add(range);
}
for (Entry<BasicBlock, Set<ExceptionRangeCFG>> ent : mapRanges.entrySet()) {
BasicBlock handler = ent.getKey();
Set<ExceptionRangeCFG> ranges = ent.getValue();
if(ranges.size() == 1) {
continue;
}
for(ExceptionRangeCFG range : ranges) {
// add some dummy instructions to prevent optimizing away the empty block
SimpleInstructionSequence seq = new SimpleInstructionSequence();
seq.addInstruction(Instruction.create(CodeConstants.opc_bipush, false, CodeConstants.GROUP_GENERAL, bytecode_version, new int[]{0}), -1);
seq.addInstruction(Instruction.create(CodeConstants.opc_pop, false, CodeConstants.GROUP_GENERAL, bytecode_version, null), -1);
BasicBlock dummyBlock = new BasicBlock(++graph.last_id);
dummyBlock.setSeq(seq);
graph.getBlocks().addWithKey(dummyBlock, dummyBlock.id);
// only exception predecessors from this range considered
List<BasicBlock> lstPredExceptions = new ArrayList<>(handler.getPredExceptions());
lstPredExceptions.retainAll(range.getProtectedRange());
// replace predecessors
for (BasicBlock pred : lstPredExceptions) {
pred.replaceSuccessor(handler, dummyBlock);
}
// replace handler
range.setHandler(dummyBlock);
// add common exception edges
Set<BasicBlock> commonHandlers = new HashSet<>(handler.getSuccExceptions());
for(BasicBlock pred : lstPredExceptions) {
commonHandlers.retainAll(pred.getSuccExceptions());
}
// TODO: more sanity checks?
for(BasicBlock commonHandler : commonHandlers) {
ExceptionRangeCFG commonRange = graph.getExceptionRange(commonHandler, handler);
dummyBlock.addSuccessorException(commonHandler);
commonRange.getProtectedRange().add(dummyBlock);
}
dummyBlock.addSuccessor(handler);
}
}
}
}

@ -54,7 +54,7 @@ public class FieldExprent extends Exprent {
@Override
public int getExprentUse() {
return instance == null ? Exprent.MULTIPLE_USES : instance.getExprentUse() & Exprent.MULTIPLE_USES;
return 0; // multiple references to a field considered dangerous in a multithreaded environment, thus no Exprent.MULTIPLE_USES set here
}
@Override

@ -112,6 +112,7 @@ public class SingleClassesTest {
@Test public void testPrivateEmptyConstructor() { doTest("pkg/TestPrivateEmptyConstructor"); }
@Test public void testSynchronizedUnprotected() { doTest("pkg/TestSynchronizedUnprotected"); }
@Test public void testInterfaceSuper() { doTest("pkg/TestInterfaceSuper"); }
@Test public void testFieldSingleAccess() { doTest("pkg/TestFieldSingleAccess"); }
// TODO: fix all below
//@Test public void testPackageInfo() { doTest("pkg/package-info"); }
@ -124,6 +125,7 @@ public class SingleClassesTest {
@Test public void testGroovyTrait() { doTest("pkg/TestGroovyTrait"); }
@Test public void testPrivateClasses() { doTest("pkg/PrivateClasses"); }
@Test public void testSuspendLambda() { doTest("pkg/TestSuspendLambdaKt"); }
@Test public void testNamedSuspendFun2Kt() { doTest("pkg/TestNamedSuspendFun2Kt"); }
private void doTest(String testFile, String... companionFiles) {
ConsoleDecompiler decompiler = fixture.getDecompiler();

@ -0,0 +1,40 @@
package pkg;
public final class TestFieldSingleAccess {
public Integer field;
public final void test() {
Integer var10000 = this.field;
if (var10000 != null) {
System.out.println(var10000);
}
}
public final void test1() {
synchronized(this.field) {
System.out.println('1');
}
}
}
class 'pkg/TestFieldSingleAccess' {
method 'test ()V' {
1 6
5 7
8 8
c 8
f 11
}
method 'test1 ()V' {
1 14
6 14
7 15
a 15
c 15
19 17
}
}
Lines mapping:

@ -3,7 +3,6 @@ public class TestMethodReferenceSameName {
private void foo() {
TestMethodReferenceSameName.R1 var10000 = this.r;// 5
this.r.getClass();
(var10000::foo).run();
}// 6
@ -16,19 +15,18 @@ public class TestMethodReferenceSameName {
class 'TestMethodReferenceSameName' {
method 'foo ()V' {
1 4
5 5
e 6
13 7
e 5
13 6
}
}
class 'TestMethodReferenceSameName$R1' {
method 'foo ()V' {
0 11
0 10
}
}
Lines mapping:
5 <-> 5
6 <-> 8
9 <-> 12
6 <-> 7
9 <-> 11

@ -0,0 +1,301 @@
import kotlin.Metadata;
import kotlin.SuccessOrFailure.Failure;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.ContinuationImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 2,
xi = 2,
d1 = {"\u0000\n\n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\u001a\u0011\u0010\u0000\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u001a\u0011\u0010\u0003\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u0082\u0002\u0004\n\u0002\b\u0019"},
d2 = {"bar", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "foo2"}
)
public final class TestNamedSuspendFun2Kt {
@Nullable
public static final Object foo2(@NotNull Continuation<? super Integer> var0) {
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 3,
xi = 2,
d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\b\u0010\u0000\u001a\u0004\u0018\u00010\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003H\u0086@ø\u0001\u0000"},
d2 = {"foo2", "", "continuation", "Lkotlin/coroutines/Continuation;", ""}
)
final class NamelessClass_1 extends ContinuationImpl {
int label;
int I$0;
Object L$0;
@Nullable
public final Object invokeSuspend(@NotNull Object result) {
this.data = result;
this.label |= -2147483648;
return TestNamedSuspendFun2Kt.foo2(this);
}
NamelessClass_1(Continuation var1) {
super(var1);
}
}
NamelessClass_1 var3;
label463: {
if (var0 instanceof NamelessClass_1) {
var3 = (NamelessClass_1)var0;
if ((var3.getLabel() & -2147483648) != 0) {
var3.setLabel(var3.getLabel() - -2147483648);
break label463;
}
}
var3 = new NamelessClass_1(var0);
}
Object var4;
int x;
label491: {
Throwable var1;
Throwable var10000;
label472: {
Object var2 = var3.data;
var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();// 2
boolean var10001;
Object var22;
switch(var3.label) {
case 0:
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
break;
case 1:
try {
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
var22 = var2;
} catch (Throwable var19) {
var10000 = var19;
var10001 = false;
break label472;
}
try {
x = ((Number)var22).intValue();// 6
if (x == 0) {
break label491;
}
} catch (Throwable var17) {
var10000 = var17;
var10001 = false;
break label472;
}
var3.label = 3;
if (bar(var3) == var4) {
return var4;
}
break;
case 2:
x = var3.I$0;
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
return 1;// 11
case 3:
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
break;
case 4:
var1 = (Throwable)var3.L$0;
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
throw var1;// 9
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
do {
try {
var3.label = 1;// 5
var22 = bar(var3);
} catch (Throwable var18) {
var10000 = var18;
var10001 = false;
break label472;
}
if (var22 == var4) {
return var4;
}
try {
x = ((Number)var22).intValue();
if (x == 0) {
break label491;
}
} catch (Throwable var20) {
var10000 = var20;
var10001 = false;
break label472;
}
var3.label = 3;
} while(bar(var3) != var4);
return var4;
}
var1 = var10000;
var3.L$0 = var1;
var3.label = 4;
if (bar(var3) == var4) {// 8
return var4;
}
throw var1;
}
var3.I$0 = x;
var3.label = 2;
if (bar(var3) == var4) {
return var4;
} else {
return 1;
}
}
@Nullable
public static final Object bar(@NotNull Continuation<? super Integer> var0) {
return 0;// 14
}
}
class 'TestNamedSuspendFun2Kt$foo2$1' {
method 'invokeSuspend (Ljava/lang/Object;)Ljava/lang/Object;' {
2 34
a 35
d 35
11 36
14 36
}
method '<init> (Lkotlin/coroutines/Continuation;)V' {
2 40
5 41
}
}
class 'TestNamedSuspendFun2Kt' {
method 'foo2 (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;' {
1 46
4 46
8 47
b 47
d 48
10 48
12 48
13 48
18 49
1b 49
1d 49
1e 49
21 50
2c 54
2e 63
31 63
32 64
35 64
38 67
3b 67
5e 69
61 69
64 70
67 70
6a 70
6f 127
70 127
73 128
79 135
7e 136
81 75
84 75
87 76
8a 76
8d 76
90 87
93 87
96 87
98 88
9e 166
a2 167
a3 167
a6 168
ac 168
b1 169
b3 103
b6 103
b9 104
bc 104
bf 105
c2 105
c5 105
c9 108
ce 97
cf 97
d2 98
d8 98
dd 99
e0 110
e3 110
e6 111
e9 111
ec 111
f3 156
f7 157
fb 158
fc 158
ff 159
105 159
10a 160
10c 115
10f 115
112 115
115 116
118 116
11b 117
11e 117
121 117
126 120
12a 108
12b 108
133 122
138 122
}
method 'bar (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;' {
0 177
1 177
4 177
}
}
Lines mapping:
2 <-> 65
5 <-> 128
6 <-> 88
8 <-> 160
9 <-> 121
11 <-> 109
14 <-> 178
Not mapped:
3
4

@ -0,0 +1,67 @@
/**
* This code can be assembled with <a href="https://wiki.openjdk.java.net/display/CodeTools/asmtools">asmtools</a>
* using <code>asmtools jasm -g *.jasm</code> command line.
*/
package pkg;
super public final class TestFieldSingleAccess
version 52:0
{
public Field field:"Ljava/lang/Integer;";
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
return;
}
public final Method test:"()V"
stack 2 locals 1
{
aload_0;
getfield Field field:"Ljava/lang/Integer;";
dup;
ifnull L17;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
swap;
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/Object;)V";
L17: stack_frame_type same;
return;
}
public final Method test1:"()V"
stack 2 locals 3
{
aload_0;
getfield Field field:"Ljava/lang/Integer;";
dup;
astore_1;
monitorenter;
try t0;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
bipush 49;
invokevirtual Method java/io/PrintStream.println:"(C)V";
aload_1;
monitorexit;
endtry t0;
goto L25;
catch t0 #0;
catch t1 #0;
try t1;
stack_frame_type full;
locals_map class TestFieldSingleAccess, class java/lang/Object;
stack_map class java/lang/Throwable;
astore_2;
aload_1;
monitorexit;
endtry t1;
aload_2;
athrow;
L25: stack_frame_type chop1;
return;
}
} // end Class TestFieldSingleAccess

@ -0,0 +1,14 @@
suspend fun foo2(): Int {
while (true) {
try {
val x = bar()
if (x == 0) break
} finally {
bar()
}
}
return 1
}
suspend fun bar(): Int = 0
Loading…
Cancel
Save