diff options
Diffstat (limited to 'plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java')
-rw-r--r-- | plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java new file mode 100644 index 000000000000..109490cc8ef9 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java @@ -0,0 +1,735 @@ +/* + * Copyright 2000-2014 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.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.*; +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.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionEdge; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionNode; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsGraph; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.util.FastSparseSetFactory.FastSparseSet; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.SFormsFastMapDirect; + +import java.util.*; +import java.util.Map.Entry; + + +public class StackVarsProcessor { + + public void simplifyStackVars(RootStatement root, StructMethod mt, StructClass cl) { + + HashSet<Integer> setReorderedIfs = new HashSet<Integer>(); + + SSAUConstructorSparseEx ssau = null; + + while (true) { + + boolean found = false; + + // System.out.println("--------------- \r\n"+root.toJava()); + + SSAConstructorSparseEx ssa = new SSAConstructorSparseEx(); + ssa.splitVariables(root, mt); + + // System.out.println("--------------- \r\n"+root.toJava()); + + + SimplifyExprentsHelper sehelper = new SimplifyExprentsHelper(ssau == null); + while (sehelper.simplifyStackVarsStatement(root, setReorderedIfs, ssa, cl)) { + // System.out.println("--------------- \r\n"+root.toJava()); + found = true; + } + + + // System.out.println("=============== \r\n"+root.toJava()); + + setVersionsToNull(root); + + SequenceHelper.condenseSequences(root); + + ssau = new SSAUConstructorSparseEx(); + ssau.splitVariables(root, mt); + + // try { + // DotExporter.toDotFile(ssau.getSsuversions(), new File("c:\\Temp\\gr12_my.dot")); + // } catch(Exception ex) { + // ex.printStackTrace(); + // } + + // System.out.println("++++++++++++++++ \r\n"+root.toJava()); + + + if (iterateStatements(root, ssau)) { + found = true; + } + + // System.out.println("***************** \r\n"+root.toJava()); + + setVersionsToNull(root); + + if (!found) { + break; + } + } + + // remove unused assignments + ssau = new SSAUConstructorSparseEx(); + ssau.splitVariables(root, mt); + + // try { + // DotExporter.toDotFile(ssau.getSsuversions(), new File("c:\\Temp\\gr12_my.dot")); + // } catch(Exception ex) { + // ex.printStackTrace(); + // } + + iterateStatements(root, ssau); + + // System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + setVersionsToNull(root); + } + + private static void setVersionsToNull(Statement stat) { + + if (stat.getExprents() == null) { + for (Object obj : stat.getSequentialObjects()) { + if (obj instanceof Statement) { + setVersionsToNull((Statement)obj); + } + else if (obj instanceof Exprent) { + setExprentVersionsToNull((Exprent)obj); + } + } + } + else { + for (Exprent exprent : stat.getExprents()) { + setExprentVersionsToNull(exprent); + } + } + } + + private static void setExprentVersionsToNull(Exprent exprent) { + + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + for (Exprent expr : lst) { + if (expr.type == Exprent.EXPRENT_VAR) { + ((VarExprent)expr).setVersion(0); + } + } + } + + + private boolean iterateStatements(RootStatement root, SSAUConstructorSparseEx ssa) { + + FlattenStatementsHelper flatthelper = new FlattenStatementsHelper(); + DirectGraph dgraph = flatthelper.buildDirectGraph(root); + + boolean res = false; + + HashSet<DirectNode> setVisited = new HashSet<DirectNode>(); + LinkedList<DirectNode> stack = new LinkedList<DirectNode>(); + LinkedList<HashMap<VarVersionPaar, Exprent>> stackMaps = new LinkedList<HashMap<VarVersionPaar, Exprent>>(); + + stack.add(dgraph.first); + stackMaps.add(new HashMap<VarVersionPaar, Exprent>()); + + while (!stack.isEmpty()) { + + DirectNode nd = stack.removeFirst(); + HashMap<VarVersionPaar, Exprent> mapVarValues = stackMaps.removeFirst(); + + if (setVisited.contains(nd)) { + continue; + } + setVisited.add(nd); + + List<List<Exprent>> lstLists = new ArrayList<List<Exprent>>(); + + if (!nd.exprents.isEmpty()) { + lstLists.add(nd.exprents); + } + + if (nd.succs.size() == 1) { + DirectNode ndsucc = nd.succs.get(0); + if (ndsucc.type == DirectNode.NODE_TAIL && !ndsucc.exprents.isEmpty()) { + lstLists.add(nd.succs.get(0).exprents); + nd = ndsucc; + } + } + + for (int i = 0; i < lstLists.size(); i++) { + List<Exprent> lst = lstLists.get(i); + + int index = 0; + while (index < lst.size()) { + Exprent next = null; + if (index == lst.size() - 1) { + if (i < lstLists.size() - 1) { + next = lstLists.get(i + 1).get(0); + } + } + else { + next = lst.get(index + 1); + } + + int[] ret = iterateExprent(lst, index, next, mapVarValues, ssa); + + //System.out.println("***************** \r\n"+root.toJava()); + + if (ret[0] >= 0) { + index = ret[0]; + } + else { + index++; + } + res |= (ret[1] == 1); + } + } + + for (DirectNode ndx : nd.succs) { + stack.add(ndx); + stackMaps.add(new HashMap<VarVersionPaar, Exprent>(mapVarValues)); + } + + // make sure the 3 special exprent lists in a loop (init, condition, increment) are not empty + // change loop type if necessary + if (nd.exprents.isEmpty() && + (nd.type == DirectNode.NODE_INIT || nd.type == DirectNode.NODE_CONDITION || nd.type == DirectNode.NODE_INCREMENT)) { + nd.exprents.add(null); + + if (nd.statement.type == Statement.TYPE_DO) { + DoStatement loop = (DoStatement)nd.statement; + + if (loop.getLooptype() == DoStatement.LOOP_FOR && + loop.getInitExprent() == null && + loop.getIncExprent() == null) { // "downgrade" loop to 'while' + loop.setLooptype(DoStatement.LOOP_WHILE); + } + } + } + } + + return res; + } + + + private static Exprent isReplaceableVar(Exprent exprent, HashMap<VarVersionPaar, Exprent> mapVarValues, SSAUConstructorSparseEx ssau) { + + Exprent dest = null; + + if (exprent.type == Exprent.EXPRENT_VAR) { + VarExprent var = (VarExprent)exprent; + dest = mapVarValues.get(new VarVersionPaar(var)); + } + + return dest; + } + + private static void replaceSingleVar(Exprent parent, VarExprent var, Exprent dest, SSAUConstructorSparseEx ssau) { + + parent.replaceExprent(var, dest); + + // live sets + SFormsFastMapDirect livemap = ssau.getLiveVarVersionsMap(new VarVersionPaar(var)); + HashSet<VarVersionPaar> setVars = getAllVersions(dest); + + for (VarVersionPaar varpaar : setVars) { + VarVersionNode node = ssau.getSsuversions().nodes.getWithKey(varpaar); + + for (Iterator<Entry<Integer, FastSparseSet<Integer>>> itent = node.live.entryList().iterator(); itent.hasNext(); ) { + Entry<Integer, FastSparseSet<Integer>> ent = itent.next(); + + Integer key = ent.getKey(); + + if (!livemap.containsKey(key)) { + itent.remove(); + } + else { + FastSparseSet<Integer> set = ent.getValue(); + + set.complement(livemap.get(key)); + if (set.isEmpty()) { + itent.remove(); + } + } + } + } + } + + private int[] iterateExprent(List<Exprent> lstExprents, int index, Exprent next, HashMap<VarVersionPaar, + Exprent> mapVarValues, SSAUConstructorSparseEx ssau) { + + Exprent exprent = lstExprents.get(index); + + int changed = 0; + + for (Exprent expr : exprent.getAllExprents()) { + while (true) { + Object[] arr = iterateChildExprent(expr, exprent, next, mapVarValues, ssau); + Exprent retexpr = (Exprent)arr[0]; + changed |= (Boolean)arr[1] ? 1 : 0; + + boolean isReplaceable = (Boolean)arr[2]; + if (retexpr != null) { + if (isReplaceable) { + replaceSingleVar(exprent, (VarExprent)expr, retexpr, ssau); + expr = retexpr; + } + else { + exprent.replaceExprent(expr, retexpr); + } + changed = 1; + } + + if (!isReplaceable) { + break; + } + } + } + + // no var on the highest level, so no replacing + + VarExprent left = null; + Exprent right = null; + + if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent as = (AssignmentExprent)exprent; + if (as.getLeft().type == Exprent.EXPRENT_VAR) { + left = (VarExprent)as.getLeft(); + right = as.getRight(); + } + } + + if (left == null) { + return new int[]{-1, changed}; + } + + VarVersionPaar leftpaar = new VarVersionPaar(left); + + List<VarVersionNode> usedVers = new ArrayList<VarVersionNode>(); + boolean notdom = getUsedVersions(ssau, leftpaar, usedVers); + + if (!notdom && usedVers.isEmpty()) { + if (left.isStack() && (right.type == Exprent.EXPRENT_INVOCATION || + right.type == Exprent.EXPRENT_ASSIGNMENT || right.type == Exprent.EXPRENT_NEW)) { + if (right.type == Exprent.EXPRENT_NEW) { + // new Object(); permitted + NewExprent nexpr = (NewExprent)right; + if (nexpr.isAnonymous() || nexpr.getNewtype().arraydim > 0 + || nexpr.getNewtype().type != CodeConstants.TYPE_OBJECT) { + return new int[]{-1, changed}; + } + } + + lstExprents.set(index, right); + return new int[]{index + 1, 1}; + } + else if (right.type == Exprent.EXPRENT_VAR) { + lstExprents.remove(index); + return new int[]{index, 1}; + } + else { + return new int[]{-1, changed}; + } + } + + int useflags = right.getExprentUse(); + + // stack variables only + if (!left.isStack() && + (right.type != Exprent.EXPRENT_VAR || ((VarExprent)right).isStack())) { // special case catch(... ex) + return new int[]{-1, changed}; + } + + if ((useflags & Exprent.MULTIPLE_USES) == 0 && (notdom || usedVers.size() > 1)) { + return new int[]{-1, changed}; + } + + HashMap<Integer, HashSet<VarVersionPaar>> mapVars = getAllVarVersions(leftpaar, right, ssau); + + boolean isSelfReference = mapVars.containsKey(leftpaar.var); + if (isSelfReference && notdom) { + return new int[]{-1, changed}; + } + + HashSet<VarVersionPaar> setNextVars = next == null ? null : getAllVersions(next); + + // FIXME: fix the entire method! + if (right.type != Exprent.EXPRENT_CONST && + right.type != Exprent.EXPRENT_VAR && + setNextVars != null && + mapVars.containsKey(leftpaar.var)) { + for (VarVersionNode usedvar : usedVers) { + if (!setNextVars.contains(new VarVersionPaar(usedvar.var, usedvar.version))) { + return new int[]{-1, changed}; + } + } + } + + mapVars.remove(leftpaar.var); + + boolean vernotreplaced = false; + boolean verreplaced = false; + + + HashSet<VarVersionPaar> setTempUsedVers = new HashSet<VarVersionPaar>(); + + for (VarVersionNode usedvar : usedVers) { + VarVersionPaar usedver = new VarVersionPaar(usedvar.var, usedvar.version); + if (isVersionToBeReplaced(usedver, mapVars, ssau, leftpaar) && + (right.type == Exprent.EXPRENT_CONST || right.type == Exprent.EXPRENT_VAR || right.type == Exprent.EXPRENT_FIELD + || setNextVars == null || setNextVars.contains(usedver))) { + + setTempUsedVers.add(usedver); + verreplaced = true; + } + else { + vernotreplaced = true; + } + } + + if (isSelfReference && vernotreplaced) { + return new int[]{-1, changed}; + } + else { + for (VarVersionPaar usedver : setTempUsedVers) { + Exprent copy = right.copy(); + if (right.type == Exprent.EXPRENT_FIELD && ssau.getMapFieldVars().containsKey(right.id)) { + ssau.getMapFieldVars().put(copy.id, ssau.getMapFieldVars().get(right.id)); + } + + mapVarValues.put(usedver, copy); + } + } + + if (!notdom && !vernotreplaced) { + // remove assignment + lstExprents.remove(index); + return new int[]{index, 1}; + } + else if (verreplaced) { + return new int[]{index + 1, changed}; + } + else { + return new int[]{-1, changed}; + } + } + + private static HashSet<VarVersionPaar> getAllVersions(Exprent exprent) { + + HashSet<VarVersionPaar> res = new HashSet<VarVersionPaar>(); + + List<Exprent> listTemp = new ArrayList<Exprent>(exprent.getAllExprents(true)); + listTemp.add(exprent); + + for (Exprent expr : listTemp) { + if (expr.type == Exprent.EXPRENT_VAR) { + VarExprent var = (VarExprent)expr; + res.add(new VarVersionPaar(var)); + } + } + + return res; + } + + private static Object[] iterateChildExprent(Exprent exprent, + Exprent parent, + Exprent next, + HashMap<VarVersionPaar, Exprent> mapVarValues, + SSAUConstructorSparseEx ssau) { + + boolean changed = false; + + for (Exprent expr : exprent.getAllExprents()) { + while (true) { + Object[] arr = iterateChildExprent(expr, parent, next, mapVarValues, ssau); + Exprent retexpr = (Exprent)arr[0]; + changed |= (Boolean)arr[1]; + + boolean isReplaceable = (Boolean)arr[2]; + if (retexpr != null) { + if (isReplaceable) { + replaceSingleVar(exprent, (VarExprent)expr, retexpr, ssau); + expr = retexpr; + } + else { + exprent.replaceExprent(expr, retexpr); + } + changed = true; + } + + if (!isReplaceable) { + break; + } + } + } + + Exprent dest = isReplaceableVar(exprent, mapVarValues, ssau); + if (dest != null) { + return new Object[]{dest, true, true}; + } + + + VarExprent left = null; + Exprent right = null; + + if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent as = (AssignmentExprent)exprent; + if (as.getLeft().type == Exprent.EXPRENT_VAR) { + left = (VarExprent)as.getLeft(); + right = as.getRight(); + } + } + + if (left == null) { + return new Object[]{null, changed, false}; + } + + boolean isHeadSynchronized = false; + if (next == null && parent.type == Exprent.EXPRENT_MONITOR) { + MonitorExprent monexpr = (MonitorExprent)parent; + if (monexpr.getMontype() == MonitorExprent.MONITOR_ENTER && exprent.equals(monexpr.getValue())) { + isHeadSynchronized = true; + } + } + + // stack variable or synchronized head exprent + if (!left.isStack() && !isHeadSynchronized) { + return new Object[]{null, changed, false}; + } + + VarVersionPaar leftpaar = new VarVersionPaar(left); + + List<VarVersionNode> usedVers = new ArrayList<VarVersionNode>(); + boolean notdom = getUsedVersions(ssau, leftpaar, usedVers); + + if (!notdom && usedVers.isEmpty()) { + return new Object[]{right, changed, false}; + } + + // stack variables only + if (!left.isStack()) { + return new Object[]{null, changed, false}; + } + + int useflags = right.getExprentUse(); + + if ((useflags & Exprent.BOTH_FLAGS) != Exprent.BOTH_FLAGS) { + return new Object[]{null, changed, false}; + } + + HashMap<Integer, HashSet<VarVersionPaar>> mapVars = getAllVarVersions(leftpaar, right, ssau); + + if (mapVars.containsKey(leftpaar.var) && notdom) { + return new Object[]{null, changed, false}; + } + + + mapVars.remove(leftpaar.var); + + HashSet<VarVersionPaar> setAllowedVars = getAllVersions(parent); + if (next != null) { + setAllowedVars.addAll(getAllVersions(next)); + } + + boolean vernotreplaced = false; + + HashSet<VarVersionPaar> setTempUsedVers = new HashSet<VarVersionPaar>(); + + for (VarVersionNode usedvar : usedVers) { + VarVersionPaar usedver = new VarVersionPaar(usedvar.var, usedvar.version); + if (isVersionToBeReplaced(usedver, mapVars, ssau, leftpaar) && + (right.type == Exprent.EXPRENT_VAR || setAllowedVars.contains(usedver))) { + + setTempUsedVers.add(usedver); + } + else { + vernotreplaced = true; + } + } + + if (!notdom && !vernotreplaced) { + + for (VarVersionPaar usedver : setTempUsedVers) { + Exprent copy = right.copy(); + if (right.type == Exprent.EXPRENT_FIELD && ssau.getMapFieldVars().containsKey(right.id)) { + ssau.getMapFieldVars().put(copy.id, ssau.getMapFieldVars().get(right.id)); + } + + mapVarValues.put(usedver, copy); + } + + // remove assignment + return new Object[]{right, changed, false}; + } + + return new Object[]{null, changed, false}; + } + + private static boolean getUsedVersions(SSAUConstructorSparseEx ssa, VarVersionPaar var, List<VarVersionNode> res) { + + VarVersionsGraph ssuversions = ssa.getSsuversions(); + VarVersionNode varnode = ssuversions.nodes.getWithKey(var); + + HashSet<VarVersionNode> setVisited = new HashSet<VarVersionNode>(); + + HashSet<VarVersionNode> setNotDoms = new HashSet<VarVersionNode>(); + + LinkedList<VarVersionNode> stack = new LinkedList<VarVersionNode>(); + stack.add(varnode); + + while (!stack.isEmpty()) { + + VarVersionNode nd = stack.remove(0); + setVisited.add(nd); + + if (nd != varnode && (nd.flags & VarVersionNode.FLAG_PHANTOM_FINEXIT) == 0) { + res.add(nd); + } + + for (VarVersionEdge edge : nd.succs) { + VarVersionNode succ = edge.dest; + + if (!setVisited.contains(edge.dest)) { + + boolean isDominated = true; + for (VarVersionEdge prededge : succ.preds) { + if (!setVisited.contains(prededge.source)) { + isDominated = false; + break; + } + } + + if (isDominated) { + stack.add(succ); + } + else { + setNotDoms.add(succ); + } + } + } + } + + setNotDoms.removeAll(setVisited); + + return !setNotDoms.isEmpty(); + } + + private static boolean isVersionToBeReplaced(VarVersionPaar usedvar, + HashMap<Integer, HashSet<VarVersionPaar>> mapVars, + SSAUConstructorSparseEx ssau, + VarVersionPaar leftpaar) { + + VarVersionsGraph ssuversions = ssau.getSsuversions(); + + SFormsFastMapDirect mapLiveVars = ssau.getLiveVarVersionsMap(usedvar); + if (mapLiveVars == null) { + // dummy version, predecessor of a phi node + return false; + } + + // compare protected ranges + if (!InterpreterUtil.equalObjects(ssau.getMapVersionFirstRange().get(leftpaar), + ssau.getMapVersionFirstRange().get(usedvar))) { + return false; + } + + for (Entry<Integer, HashSet<VarVersionPaar>> ent : mapVars.entrySet()) { + FastSparseSet<Integer> liveverset = mapLiveVars.get(ent.getKey()); + if (liveverset == null) { + return false; + } + + HashSet<VarVersionNode> domset = new HashSet<VarVersionNode>(); + for (VarVersionPaar verpaar : ent.getValue()) { + domset.add(ssuversions.nodes.getWithKey(verpaar)); + } + + boolean isdom = false; + + for (Integer livever : liveverset) { + VarVersionNode node = ssuversions.nodes.getWithKey(new VarVersionPaar(ent.getKey().intValue(), livever.intValue())); + + if (ssuversions.isDominatorSet(node, domset)) { + isdom = true; + break; + } + } + + if (!isdom) { + return false; + } + } + + return true; + } + + private static HashMap<Integer, HashSet<VarVersionPaar>> getAllVarVersions(VarVersionPaar leftvar, + Exprent exprent, + SSAUConstructorSparseEx ssau) { + + HashMap<Integer, HashSet<VarVersionPaar>> map = new HashMap<Integer, HashSet<VarVersionPaar>>(); + SFormsFastMapDirect mapLiveVars = ssau.getLiveVarVersionsMap(leftvar); + + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + for (Exprent expr : lst) { + if (expr.type == Exprent.EXPRENT_VAR) { + int varindex = ((VarExprent)expr).getIndex(); + if (leftvar.var != varindex) { + if (mapLiveVars.containsKey(varindex)) { + HashSet<VarVersionPaar> verset = new HashSet<VarVersionPaar>(); + for (Integer vers : mapLiveVars.get(varindex)) { + verset.add(new VarVersionPaar(varindex, vers.intValue())); + } + map.put(varindex, verset); + } + else { + throw new RuntimeException("inkonsistent live map!"); + } + } + else { + map.put(varindex, null); + } + } + else if (expr.type == Exprent.EXPRENT_FIELD) { + if (ssau.getMapFieldVars().containsKey(expr.id)) { + int varindex = ssau.getMapFieldVars().get(expr.id); + if (mapLiveVars.containsKey(varindex)) { + HashSet<VarVersionPaar> verset = new HashSet<VarVersionPaar>(); + for (Integer vers : mapLiveVars.get(varindex)) { + verset.add(new VarVersionPaar(varindex, vers.intValue())); + } + map.put(varindex, verset); + } + } + } + } + + return map; + } +} |