IDEA-127499 Decompiler doesn't support switch over enums
This commit is contained in:
@@ -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) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
testData/classes/pkg/TestSwitchOnStrings.class
Normal file
BIN
testData/classes/pkg/TestSwitchOnStrings.class
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
13
testData/results/TestSwitchOnStrings.dec
Normal file
13
testData/results/TestSwitchOnStrings.dec
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
13
testData/src/pkg/TestSwitchOnStrings.java
Normal file
13
testData/src/pkg/TestSwitchOnStrings.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user