IDEA-127499 Decompiler doesn't support switch over enums

master
Egor.Ushakov 7 years ago
parent 2e9dfd2437
commit a62cc3f709
  1. 94
      src/org/jetbrains/java/decompiler/modules/decompiler/SwitchHelper.java
  2. 7
      src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java
  3. 14
      src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchExprent.java
  4. 38
      src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java
  5. 3
      test/org/jetbrains/java/decompiler/SingleClassesTest.java
  6. BIN
      testData/classes/pkg/TestSwitchOnEnum$1.class
  7. BIN
      testData/classes/pkg/TestSwitchOnEnum.class
  8. BIN
      testData/classes/pkg/TestSwitchOnStrings.class
  9. 33
      testData/results/TestSwitchOnEnum.dec
  10. 13
      testData/results/TestSwitchOnStrings.dec
  11. 2
      testData/src/pkg/TestSwitchOnEnum.java
  12. 13
      testData/src/pkg/TestSwitchOnStrings.java

@ -0,0 +1,94 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.java.decompiler.modules.decompiler;
import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.main.ClassesProcessor;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import org.jetbrains.java.decompiler.main.rels.MethodWrapper;
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph;
import org.jetbrains.java.decompiler.modules.decompiler.stats.SwitchStatement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SwitchHelper {
public static void simplify(SwitchStatement switchStatement) {
SwitchExprent switchExprent = (SwitchExprent)switchStatement.getHeadexprent();
Exprent value = switchExprent.getValue();
if (isEnumArray(value)) {
List<List<Exprent>> caseValues = switchStatement.getCaseValues();
Map<Exprent, Exprent> mapping = new HashMap<>(caseValues.size());
ArrayExprent array = (ArrayExprent)value;
ClassesProcessor.ClassNode classNode =
DecompilerContext.getClassProcessor().getMapRootClasses().get(((FieldExprent)array.getArray()).getClassname());
if (classNode != null) {
MethodWrapper wrapper = classNode.getWrapper().getMethodWrapper(CodeConstants.CLINIT_NAME, "()V");
if (wrapper != null) {
wrapper.getOrBuildGraph().iterateExprents(new DirectGraph.ExprentIterator() {
@Override
public int processExprent(Exprent exprent) {
if (exprent instanceof AssignmentExprent) {
AssignmentExprent assignment = (AssignmentExprent)exprent;
Exprent left = assignment.getLeft();
if (isEnumArray(left)) {
mapping.put(assignment.getRight(), ((InvocationExprent)((ArrayExprent)left).getIndex()).getInstance());
}
}
return 0;
}
});
}
}
List<List<Exprent>> realCaseValues = new ArrayList<>(caseValues.size());
for (List<Exprent> caseValue : caseValues) {
List<Exprent> values = new ArrayList<>(caseValue.size());
realCaseValues.add(values);
for (Exprent exprent : caseValue) {
if (exprent == null) {
values.add(null);
}
else {
Exprent realConst = mapping.get(exprent);
if (realConst == null) {
DecompilerContext.getLogger()
.writeMessage("Unable to simplify switch on enum: " + exprent + " not found, available: " + mapping,
IFernflowerLogger.Severity.ERROR);
return;
}
values.add(realConst.copy());
}
}
}
caseValues.clear();
caseValues.addAll(realCaseValues);
switchExprent.replaceExprent(value, ((InvocationExprent)array.getIndex()).getInstance().copy());
}
}
private static boolean isEnumArray(Exprent exprent) {
if (exprent instanceof ArrayExprent) {
Exprent field = ((ArrayExprent)exprent).getArray();
return field instanceof FieldExprent && ((FieldExprent)field).getName().startsWith("$SwitchMap");
}
return false;
}
}

@ -300,6 +300,13 @@ public class ConstExprent extends Exprent {
InterpreterUtil.equalObjects(value, cn.getValue());
}
@Override
public int hashCode() {
int result = constType != null ? constType.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
public boolean hasBooleanValue() {
switch (constType.type) {
case CodeConstants.TYPE_BOOLEAN:

@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ import java.util.Set;
public class SwitchExprent extends Exprent {
private Exprent value;
private List<List<ConstExprent>> caseValues = new ArrayList<>();
private List<List<Exprent>> caseValues = new ArrayList<>();
public SwitchExprent(Exprent value, Set<Integer> bytecodeOffsets) {
super(EXPRENT_SWITCH);
@ -41,8 +41,8 @@ public class SwitchExprent extends Exprent {
public Exprent copy() {
SwitchExprent swExpr = new SwitchExprent(value.copy(), bytecode);
List<List<ConstExprent>> lstCaseValues = new ArrayList<>();
for (List<ConstExprent> lst : caseValues) {
List<List<Exprent>> lstCaseValues = new ArrayList<>();
for (List<Exprent> lst : caseValues) {
lstCaseValues.add(new ArrayList<>(lst));
}
swExpr.setCaseValues(lstCaseValues);
@ -63,8 +63,8 @@ public class SwitchExprent extends Exprent {
result.addMaxTypeExprent(value, VarType.VARTYPE_INT);
VarType valType = value.getExprType();
for (List<ConstExprent> lst : caseValues) {
for (ConstExprent expr : lst) {
for (List<Exprent> lst : caseValues) {
for (Exprent expr : lst) {
if (expr != null) {
VarType caseType = expr.getExprType();
if (!caseType.equals(valType)) {
@ -116,7 +116,7 @@ public class SwitchExprent extends Exprent {
return value;
}
public void setCaseValues(List<List<ConstExprent>> caseValues) {
public void setCaseValues(List<List<Exprent>> caseValues) {
this.caseValues = caseValues;
}
}

@ -24,8 +24,10 @@ import org.jetbrains.java.decompiler.main.collectors.CounterContainer;
import org.jetbrains.java.decompiler.modules.decompiler.DecHelper;
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
import org.jetbrains.java.decompiler.modules.decompiler.StatEdge;
import org.jetbrains.java.decompiler.modules.decompiler.SwitchHelper;
import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.SwitchExprent;
import org.jetbrains.java.decompiler.struct.gen.VarType;
@ -41,7 +43,7 @@ public class SwitchStatement extends Statement {
private List<List<StatEdge>> caseEdges = new ArrayList<>();
private List<List<ConstExprent>> caseValues = new ArrayList<>();
private List<List<Exprent>> caseValues = new ArrayList<>();
private StatEdge default_edge;
@ -108,6 +110,8 @@ public class SwitchStatement extends Statement {
}
public TextBuffer toJava(int indent, BytecodeMappingTracer tracer) {
SwitchHelper.simplify(this);
TextBuffer buf = new TextBuffer();
buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer));
buf.append(first.toJava(indent, tracer));
@ -126,7 +130,7 @@ public class SwitchStatement extends Statement {
Statement stat = caseStatements.get(i);
List<StatEdge> edges = caseEdges.get(i);
List<ConstExprent> values = caseValues.get(i);
List<Exprent> values = caseValues.get(i);
for (int j = 0; j < edges.size(); j++) {
if (edges.get(j) == default_edge) {
@ -134,10 +138,20 @@ public class SwitchStatement extends Statement {
tracer.incrementCurrentSourceLine();
}
else {
ConstExprent value = (ConstExprent)values.get(j).copy();
value.setConstType(switch_type);
buf.appendIndent(indent).append("case ");
Exprent value = values.get(j);
if (value instanceof ConstExprent) {
value = value.copy();
((ConstExprent)value).setConstType(switch_type);
}
if (value instanceof FieldExprent && ((FieldExprent)value).isStatic()) { // enum values
buf.append(((FieldExprent)value).getName());
}
else {
buf.append(value.toJava(indent, tracer));
}
buf.appendIndent(indent).append("case ").append(value.toJava(indent, tracer)).append(":").appendLineSeparator();
buf.append(":").appendLineSeparator();
tracer.incrementCurrentSourceLine();
}
}
@ -211,8 +225,8 @@ public class SwitchStatement extends Statement {
BasicBlockStatement bbstat = (BasicBlockStatement)first;
int[] values = ((SwitchInstruction)bbstat.getBlock().getLastInstruction()).getValues();
List<Statement> nodes = new ArrayList<>();
List<List<Integer>> edges = new ArrayList<>();
List<Statement> nodes = new ArrayList<>(stats.size() - 1);
List<List<Integer>> edges = new ArrayList<>(stats.size() - 1);
// collect regular edges
for (int i = 1; i < stats.size(); i++) {
@ -293,12 +307,12 @@ public class SwitchStatement extends Statement {
}
// translate indices back into edges
List<List<StatEdge>> lstEdges = new ArrayList<>();
List<List<ConstExprent>> lstValues = new ArrayList<>();
List<List<StatEdge>> lstEdges = new ArrayList<>(edges.size());
List<List<Exprent>> lstValues = new ArrayList<>(edges.size());
for (List<Integer> lst : edges) {
List<StatEdge> lste = new ArrayList<>();
List<ConstExprent> lstv = new ArrayList<>();
List<StatEdge> lste = new ArrayList<>(lst.size());
List<Exprent> lstv = new ArrayList<>(lst.size());
List<StatEdge> lstSuccs = first.getSuccessorEdges(STATEDGE_DIRECT_ALL);
for (Integer in : lst) {
@ -362,7 +376,7 @@ public class SwitchStatement extends Statement {
return default_edge;
}
public List<List<ConstExprent>> getCaseValues() {
public List<List<Exprent>> getCaseValues() {
return caseValues;
}
}

@ -105,7 +105,8 @@ public class SingleClassesTest {
@Test public void testClashName() { doTest("pkg/TestClashName", "pkg/SharedName1",
"pkg/SharedName2", "pkg/SharedName3", "pkg/SharedName4", "pkg/NonSharedName",
"pkg/TestClashNameParent", "ext/TestClashNameParent","pkg/TestClashNameIface", "ext/TestClashNameIface"); }
@Test public void testSwitchOnEnum() { doTest("pkg/TestSwitchOnEnum","pkg/TestSwitchOnEnum$1");}
@Test public void testSwitchOnEnum() { doTest("pkg/TestSwitchOnEnum");}
//@Test public void TestSwitchOnStrings() { doTest("pkg/TestSwitchOnStrings");}
@Test public void testVarArgCalls() { doTest("pkg/TestVarArgCalls"); }
private void doTest(String testFile, String... companionFiles) {

@ -5,27 +5,32 @@ import java.util.concurrent.TimeUnit;
public class TestSwitchOnEnum {
int myInt;
public void testSOE(TimeUnit var1) {
switch(null.$SwitchMap$java$util$concurrent$TimeUnit[var1.ordinal()]) {// 12
case 1:
return;// 14
public int testSOE(TimeUnit t) {
switch(t) {// 14
case MICROSECONDS:
return 2;// 16
case SECONDS:
return 1;// 18
default:
return 0;// 20
}
}// 16
}
}
class 'pkg/TestSwitchOnEnum' {
method 'testSOE (Ljava/util/concurrent/TimeUnit;)V' {
0 8
4 8
7 8
method 'testSOE (Ljava/util/concurrent/TimeUnit;)I' {
8 8
1c 10
1d 13
24 10
25 10
26 12
27 12
28 14
29 14
}
}
Lines mapping:
12 <-> 9
14 <-> 11
16 <-> 14
14 <-> 9
16 <-> 11
18 <-> 13
20 <-> 15

@ -0,0 +1,13 @@
package pkg;
public class TestSwitchOnStrings {
public int testSOE(String s) {
switch (s) {
case "xxx":
return 2;
case "yyy":
return 1;
}
return 0;
}
}

@ -12,6 +12,8 @@ public class TestSwitchOnEnum {
public int testSOE(TimeUnit t) {
// This creates anonymous SwitchMap inner class.
switch (t) {
case MICROSECONDS:
return 2;
case SECONDS:
return 1;
}

@ -0,0 +1,13 @@
package pkg;
public class TestSwitchOnStrings {
public int testSOE(String s) {
switch (s) {
case "xxx":
return 2;
case "yyy":
return 1;
}
return 0;
}
}
Loading…
Cancel
Save