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());
|
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() {
|
public boolean hasBooleanValue() {
|
||||||
switch (constType.type) {
|
switch (constType.type) {
|
||||||
case CodeConstants.TYPE_BOOLEAN:
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
public class SwitchExprent extends Exprent {
|
||||||
|
|
||||||
private Exprent value;
|
private Exprent value;
|
||||||
private List<List<ConstExprent>> caseValues = new ArrayList<>();
|
private List<List<Exprent>> caseValues = new ArrayList<>();
|
||||||
|
|
||||||
public SwitchExprent(Exprent value, Set<Integer> bytecodeOffsets) {
|
public SwitchExprent(Exprent value, Set<Integer> bytecodeOffsets) {
|
||||||
super(EXPRENT_SWITCH);
|
super(EXPRENT_SWITCH);
|
||||||
@@ -41,8 +41,8 @@ public class SwitchExprent extends Exprent {
|
|||||||
public Exprent copy() {
|
public Exprent copy() {
|
||||||
SwitchExprent swExpr = new SwitchExprent(value.copy(), bytecode);
|
SwitchExprent swExpr = new SwitchExprent(value.copy(), bytecode);
|
||||||
|
|
||||||
List<List<ConstExprent>> lstCaseValues = new ArrayList<>();
|
List<List<Exprent>> lstCaseValues = new ArrayList<>();
|
||||||
for (List<ConstExprent> lst : caseValues) {
|
for (List<Exprent> lst : caseValues) {
|
||||||
lstCaseValues.add(new ArrayList<>(lst));
|
lstCaseValues.add(new ArrayList<>(lst));
|
||||||
}
|
}
|
||||||
swExpr.setCaseValues(lstCaseValues);
|
swExpr.setCaseValues(lstCaseValues);
|
||||||
@@ -63,8 +63,8 @@ public class SwitchExprent extends Exprent {
|
|||||||
result.addMaxTypeExprent(value, VarType.VARTYPE_INT);
|
result.addMaxTypeExprent(value, VarType.VARTYPE_INT);
|
||||||
|
|
||||||
VarType valType = value.getExprType();
|
VarType valType = value.getExprType();
|
||||||
for (List<ConstExprent> lst : caseValues) {
|
for (List<Exprent> lst : caseValues) {
|
||||||
for (ConstExprent expr : lst) {
|
for (Exprent expr : lst) {
|
||||||
if (expr != null) {
|
if (expr != null) {
|
||||||
VarType caseType = expr.getExprType();
|
VarType caseType = expr.getExprType();
|
||||||
if (!caseType.equals(valType)) {
|
if (!caseType.equals(valType)) {
|
||||||
@@ -116,7 +116,7 @@ public class SwitchExprent extends Exprent {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCaseValues(List<List<ConstExprent>> caseValues) {
|
public void setCaseValues(List<List<Exprent>> caseValues) {
|
||||||
this.caseValues = 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.DecHelper;
|
||||||
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
|
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
|
||||||
import org.jetbrains.java.decompiler.modules.decompiler.StatEdge;
|
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.ConstExprent;
|
||||||
import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent;
|
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.modules.decompiler.exps.SwitchExprent;
|
||||||
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
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<StatEdge>> caseEdges = new ArrayList<>();
|
||||||
|
|
||||||
private List<List<ConstExprent>> caseValues = new ArrayList<>();
|
private List<List<Exprent>> caseValues = new ArrayList<>();
|
||||||
|
|
||||||
private StatEdge default_edge;
|
private StatEdge default_edge;
|
||||||
|
|
||||||
@@ -108,6 +110,8 @@ public class SwitchStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TextBuffer toJava(int indent, BytecodeMappingTracer tracer) {
|
public TextBuffer toJava(int indent, BytecodeMappingTracer tracer) {
|
||||||
|
SwitchHelper.simplify(this);
|
||||||
|
|
||||||
TextBuffer buf = new TextBuffer();
|
TextBuffer buf = new TextBuffer();
|
||||||
buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer));
|
buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer));
|
||||||
buf.append(first.toJava(indent, tracer));
|
buf.append(first.toJava(indent, tracer));
|
||||||
@@ -126,7 +130,7 @@ public class SwitchStatement extends Statement {
|
|||||||
|
|
||||||
Statement stat = caseStatements.get(i);
|
Statement stat = caseStatements.get(i);
|
||||||
List<StatEdge> edges = caseEdges.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++) {
|
for (int j = 0; j < edges.size(); j++) {
|
||||||
if (edges.get(j) == default_edge) {
|
if (edges.get(j) == default_edge) {
|
||||||
@@ -134,10 +138,20 @@ public class SwitchStatement extends Statement {
|
|||||||
tracer.incrementCurrentSourceLine();
|
tracer.incrementCurrentSourceLine();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ConstExprent value = (ConstExprent)values.get(j).copy();
|
buf.appendIndent(indent).append("case ");
|
||||||
value.setConstType(switch_type);
|
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();
|
tracer.incrementCurrentSourceLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,8 +225,8 @@ public class SwitchStatement extends Statement {
|
|||||||
BasicBlockStatement bbstat = (BasicBlockStatement)first;
|
BasicBlockStatement bbstat = (BasicBlockStatement)first;
|
||||||
int[] values = ((SwitchInstruction)bbstat.getBlock().getLastInstruction()).getValues();
|
int[] values = ((SwitchInstruction)bbstat.getBlock().getLastInstruction()).getValues();
|
||||||
|
|
||||||
List<Statement> nodes = new ArrayList<>();
|
List<Statement> nodes = new ArrayList<>(stats.size() - 1);
|
||||||
List<List<Integer>> edges = new ArrayList<>();
|
List<List<Integer>> edges = new ArrayList<>(stats.size() - 1);
|
||||||
|
|
||||||
// collect regular edges
|
// collect regular edges
|
||||||
for (int i = 1; i < stats.size(); i++) {
|
for (int i = 1; i < stats.size(); i++) {
|
||||||
@@ -293,12 +307,12 @@ public class SwitchStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// translate indices back into edges
|
// translate indices back into edges
|
||||||
List<List<StatEdge>> lstEdges = new ArrayList<>();
|
List<List<StatEdge>> lstEdges = new ArrayList<>(edges.size());
|
||||||
List<List<ConstExprent>> lstValues = new ArrayList<>();
|
List<List<Exprent>> lstValues = new ArrayList<>(edges.size());
|
||||||
|
|
||||||
for (List<Integer> lst : edges) {
|
for (List<Integer> lst : edges) {
|
||||||
List<StatEdge> lste = new ArrayList<>();
|
List<StatEdge> lste = new ArrayList<>(lst.size());
|
||||||
List<ConstExprent> lstv = new ArrayList<>();
|
List<Exprent> lstv = new ArrayList<>(lst.size());
|
||||||
|
|
||||||
List<StatEdge> lstSuccs = first.getSuccessorEdges(STATEDGE_DIRECT_ALL);
|
List<StatEdge> lstSuccs = first.getSuccessorEdges(STATEDGE_DIRECT_ALL);
|
||||||
for (Integer in : lst) {
|
for (Integer in : lst) {
|
||||||
@@ -362,7 +376,7 @@ public class SwitchStatement extends Statement {
|
|||||||
return default_edge;
|
return default_edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<List<ConstExprent>> getCaseValues() {
|
public List<List<Exprent>> getCaseValues() {
|
||||||
return caseValues;
|
return caseValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ public class SingleClassesTest {
|
|||||||
@Test public void testClashName() { doTest("pkg/TestClashName", "pkg/SharedName1",
|
@Test public void testClashName() { doTest("pkg/TestClashName", "pkg/SharedName1",
|
||||||
"pkg/SharedName2", "pkg/SharedName3", "pkg/SharedName4", "pkg/NonSharedName",
|
"pkg/SharedName2", "pkg/SharedName3", "pkg/SharedName4", "pkg/NonSharedName",
|
||||||
"pkg/TestClashNameParent", "ext/TestClashNameParent","pkg/TestClashNameIface", "ext/TestClashNameIface"); }
|
"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"); }
|
@Test public void testVarArgCalls() { doTest("pkg/TestVarArgCalls"); }
|
||||||
|
|
||||||
private void doTest(String testFile, String... companionFiles) {
|
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 {
|
public class TestSwitchOnEnum {
|
||||||
int myInt;
|
int myInt;
|
||||||
|
|
||||||
public void testSOE(TimeUnit var1) {
|
public int testSOE(TimeUnit t) {
|
||||||
switch(null.$SwitchMap$java$util$concurrent$TimeUnit[var1.ordinal()]) {// 12
|
switch(t) {// 14
|
||||||
case 1:
|
case MICROSECONDS:
|
||||||
return;// 14
|
return 2;// 16
|
||||||
|
case SECONDS:
|
||||||
|
return 1;// 18
|
||||||
default:
|
default:
|
||||||
|
return 0;// 20
|
||||||
}
|
}
|
||||||
}// 16
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class 'pkg/TestSwitchOnEnum' {
|
class 'pkg/TestSwitchOnEnum' {
|
||||||
method 'testSOE (Ljava/util/concurrent/TimeUnit;)V' {
|
method 'testSOE (Ljava/util/concurrent/TimeUnit;)I' {
|
||||||
0 8
|
|
||||||
4 8
|
|
||||||
7 8
|
|
||||||
8 8
|
8 8
|
||||||
1c 10
|
24 10
|
||||||
1d 13
|
25 10
|
||||||
|
26 12
|
||||||
|
27 12
|
||||||
|
28 14
|
||||||
|
29 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Lines mapping:
|
Lines mapping:
|
||||||
12 <-> 9
|
14 <-> 9
|
||||||
14 <-> 11
|
16 <-> 11
|
||||||
16 <-> 14
|
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) {
|
public int testSOE(TimeUnit t) {
|
||||||
// This creates anonymous SwitchMap inner class.
|
// This creates anonymous SwitchMap inner class.
|
||||||
switch (t) {
|
switch (t) {
|
||||||
|
case MICROSECONDS:
|
||||||
|
return 2;
|
||||||
case SECONDS:
|
case SECONDS:
|
||||||
return 1;
|
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