From 2ee6d669136d9feb019cdc2791c61bfb752b570a Mon Sep 17 00:00:00 2001 From: Lex Manos Date: Thu, 15 Oct 2015 00:15:50 -0700 Subject: Import "Enhance switches on enums." from LexManos / MinecraftForge --- .../decompiler/main/rels/NestedClassProcessor.java | 127 +++++++++++++++++++++ .../modules/decompiler/stats/SwitchStatement.java | 54 ++++++++- .../java/decompiler/struct/StructClass.java | 3 + 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java index 25ff812..1882346 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java @@ -16,6 +16,7 @@ package org.jetbrains.java.decompiler.main.rels; import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.cfg.BasicBlock; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; @@ -25,8 +26,11 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectNode; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.CatchStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.DoStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.SequenceStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; @@ -58,6 +62,10 @@ public class NestedClassProcessor { return; } + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + gatherEnumSwitchMaps(node); + } + if (node.type != ClassNode.CLASS_LAMBDA) { computeLocalVarsAndDefinitions(node); @@ -102,6 +110,125 @@ public class NestedClassProcessor { } } + /** + * When Java introduced Enums they aded the ability to use them in Switch statements. + * This was done in a purely syntax sugar way using the old switch on int methods. + * The compiler creates a synthetic class with a static int array field. + * To support enums changing post compile, It initializes this field with a length of the current enum length. + * And then for every referenced enum value it adds a mapping in the form of: + * try { + * field[Enum.VALUE.ordinal()] = 1; + * } catch (FieldNotFoundException e) {} + * + * If a class has multiple switches on multiple enums, the compiler adds the init and try list to the BEGINNING of the static initalizer. + * But they add the field to the END of the fields list. + */ + private void gatherEnumSwitchMaps(ClassNode node) { + for (ClassNode child : node.nested) { + gatherEnumSwitchMaps(child); + } + + MethodWrapper clinit = node.wrapper.getMethodWrapper("", "()V"); + if (clinit == null || clinit.root == null || clinit.root.getFirst().type != Statement.TYPE_SEQUENCE) { + return; + } + + final int STATIC_FINAL_SYNTHETIC = CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_SYNTHETIC; + Set potentialFields = new HashSet(); + for (StructField fd : node.classStruct.getFields()) { + if ((fd.getAccessFlags() & STATIC_FINAL_SYNTHETIC) == STATIC_FINAL_SYNTHETIC && "[I".equals(fd.getDescriptor())) { + potentialFields.add(fd.getName()); + } + } + + if (potentialFields.size() == 0) { + return; + } + + SequenceStatement seq = (SequenceStatement)clinit.root.getFirst(); + for (int x = 0; x < seq.getStats().size();) { + Statement stat = seq.getStats().get(x); + if (stat.type != Statement.TYPE_BASICBLOCK || stat.getExprents() == null || stat.getExprents().size() != 1 || stat.getExprents().get(0).type != Exprent.EXPRENT_ASSIGNMENT) { + break; + } + AssignmentExprent ass = (AssignmentExprent)stat.getExprents().get(0); + if (ass.getLeft().type != Exprent.EXPRENT_FIELD || ass.getRight().type != Exprent.EXPRENT_NEW) { + break; + } + FieldExprent mapField = (FieldExprent)ass.getLeft(); + NewExprent _new = ((NewExprent)ass.getRight()); + if (!mapField.getClassname().equals(node.classStruct.qualifiedName) || !potentialFields.contains(mapField.getName()) || + _new.getNewtype().type != CodeConstants.TYPE_INT || _new.getNewtype().arraydim != 1 || + _new.getLstDims().size() != 1 || _new.getLstDims().get(0).type != Exprent.EXPRENT_FUNCTION) { + break; + } + FunctionExprent func = (FunctionExprent)_new.getLstDims().get(0); + if (func.getFunctype() != FunctionExprent.FUNCTION_ARRAYLENGTH || func.getLstOperands().size() != 1 || func.getLstOperands().get(0).type != Exprent.EXPRENT_INVOCATION) { + break; + } + InvocationExprent invoc = (InvocationExprent)func.getLstOperands().get(0); + if (!"values".equals(invoc.getName()) || !("()[L" + invoc.getClassname() + ";").equals(invoc.getStringDescriptor())) { + break; + } + + String fieldName = mapField.getName(); + String enumName = invoc.getClassname(); + Map idToName = new HashMap(); + + boolean replace = false; + int y = x; + while (++y < seq.getStats().size()) { + if (seq.getStats().get(y).type != Statement.TYPE_TRYCATCH) { + break; + } + CatchStatement _try = (CatchStatement)seq.getStats().get(y); + Statement first = _try.getFirst(); + List exprents = first.getExprents(); + if (_try.getVars().size() != 1 || !"java/lang/NoSuchFieldError".equals(_try.getVars().get(0).getVartype().value) || + first.type != Statement.TYPE_BASICBLOCK || exprents == null || exprents.size() != 1 || exprents.get(0).type != Exprent.EXPRENT_ASSIGNMENT) { + break; + } + ass = (AssignmentExprent)exprents.get(0); + if (ass.getRight().type != Exprent.EXPRENT_CONST || (!(((ConstExprent)ass.getRight()).getValue() instanceof Integer)) || + ass.getLeft().type != Exprent.EXPRENT_ARRAY){ + break; + } + ArrayExprent array = (ArrayExprent)ass.getLeft(); + if (array.getArray().type != Exprent.EXPRENT_FIELD || !array.getArray().equals(mapField) || array.getIndex().type != Exprent.EXPRENT_INVOCATION) { + break; + } + invoc = (InvocationExprent)array.getIndex(); + if (!enumName.equals(invoc.getClassname()) || !"ordinal".equals(invoc.getName()) || !"()I".equals(invoc.getStringDescriptor()) || + invoc.getInstance().type != Exprent.EXPRENT_FIELD) { + break; + } + + FieldExprent enumField = (FieldExprent)invoc.getInstance(); + if (!enumName.equals(enumField.getClassname()) || !enumField.isStatic()) { + break; + } + + idToName.put((Integer)((ConstExprent)ass.getRight()).getValue(), enumField.getName()); + seq.replaceStatement(_try, getNewEmptyStatement()); + replace = true; + } + + if (replace) { + seq.replaceStatement(seq.getStats().get(x), getNewEmptyStatement()); + node.classStruct.enumSwitchMap.put(fieldName, idToName); + node.wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fieldName, "[I")); + } + x = y; + } + } + + private Statement getNewEmptyStatement() { + BasicBlockStatement bstat = new BasicBlockStatement(new BasicBlock( + DecompilerContext.getCounterContainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER))); + bstat.setExprents(new ArrayList()); + return bstat; + } + private static void setLambdaVars(ClassNode parent, ClassNode child) { if (child.lambda_information.is_method_reference) { // method reference, no code and no parameters diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java index b0ec5e3..694baae 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java @@ -24,6 +24,7 @@ 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.exps.*; +import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.Util; @@ -137,7 +138,12 @@ public class SwitchStatement extends Statement { tracer.incrementCurrentSourceLine(); } - buf.append(indstr).append(headexprent.get(0).toJava(indent, tracer)).append(" {").append(new_line_separator); + // Doesn't seem to be a better place to put it so enhance things here + Map remaps = enhanceHead(headexprent.get(0), buf, indent, tracer); + + if (remaps == null) { + buf.append(indstr).append(headexprent.get(0).toJava(indent, tracer)).append(" {").append(new_line_separator); + } tracer.incrementCurrentSourceLine(); VarType switch_type = headexprent.get(0).getExprType(); @@ -157,7 +163,13 @@ public class SwitchStatement extends Statement { ConstExprent value = (ConstExprent)values.get(j).copy(); value.setConsttype(switch_type); - buf.append(indstr).append("case ").append(value.toJava(indent, tracer)).append(":").append(new_line_separator); + buf.append(indstr).append("case "); + if (remaps == null) { + buf.append(value.toJava(indent, tracer)); + } else { + buf.append(remaps.get(value.getValue())); + } + buf.append(":").append(new_line_separator); tracer.incrementCurrentSourceLine(); } } @@ -176,6 +188,44 @@ public class SwitchStatement extends Statement { return buf.toString(); } + private Map enhanceHead(Exprent exprent, StringBuilder buf, int indent, BytecodeMappingTracer tracer) { + if (exprent.type != Exprent.EXPRENT_SWITCH) return null; + + SwitchExprent swtch = (SwitchExprent)exprent; + if (swtch.getValue().type != Exprent.EXPRENT_ARRAY) return null; + + ArrayExprent array = (ArrayExprent)swtch.getValue(); + if (array.getArray().type != Exprent.EXPRENT_FIELD || array.getIndex().type != Exprent.EXPRENT_INVOCATION) return null; + + FieldExprent field = (FieldExprent)array.getArray(); + InvocationExprent invoc = (InvocationExprent)array.getIndex(); + StructClass cls = DecompilerContext.getStructContext().getClass(field.getClassname()); + if (cls == null || !field.isStatic() || !"ordinal".equals(invoc.getName()) || !"()I".equals(invoc.getStringDescriptor())) return null; + + Map ret = cls.enumSwitchMap.get(field.getName()); + if (ret == null) return null; + + for (List lst : getCaseValues()) { + if (lst != null) { + for (ConstExprent cst : lst) { + if (cst != null && (!(cst.getValue() instanceof Integer) || !ret.containsKey(cst.getValue()))) { + return null; + } + } + } + } + + tracer.addMapping(swtch.bytecode); + tracer.addMapping(field.bytecode); + tracer.addMapping(invoc.bytecode); + + String indstr = InterpreterUtil.getIndentString(indent); + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + buf.append(indstr).append("switch (").append((invoc.getInstance().toJava(indent, tracer))).append(") {").append(new_line_separator); + return ret; + } + public void initExprents() { SwitchExprent swexpr = (SwitchExprent)first.getExprents().remove(first.getExprents().size() - 1); swexpr.setCaseValues(caseValues); diff --git a/src/org/jetbrains/java/decompiler/struct/StructClass.java b/src/org/jetbrains/java/decompiler/struct/StructClass.java index cb24ff6..13f5875 100644 --- a/src/org/jetbrains/java/decompiler/struct/StructClass.java +++ b/src/org/jetbrains/java/decompiler/struct/StructClass.java @@ -24,6 +24,8 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.VBStyleCollection; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /* class_file { @@ -58,6 +60,7 @@ public class StructClass extends StructMember { private final String[] interfaceNames; private final VBStyleCollection fields; private final VBStyleCollection methods; + public final Map> enumSwitchMap = new HashMap>(); private ConstantPool pool; -- cgit v1.2.3