Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,11 @@
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectEdge;
import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectEdgeType;
import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph;
import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectNode;
import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectNodeType;
import org.jetbrains.java.decompiler.modules.decompiler.flow.FlattenStatementsHelper;
import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent.FunctionType;
import org.jetbrains.java.decompiler.modules.decompiler.sforms.*;
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.Statement;
import org.jetbrains.java.decompiler.modules.decompiler.flow.*;
import org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAConstructorSparseEx;
import org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx;
import org.jetbrains.java.decompiler.modules.decompiler.stats.*;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionNode;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsGraph;
Expand All @@ -27,9 +20,8 @@
import org.jetbrains.java.decompiler.struct.gen.CodeType;
import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.util.DotExporter;
import org.jetbrains.java.decompiler.util.collections.FastSparseSetFactory.FastSparseSet;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.collections.FastSparseSetFactory.FastSparseSet;
import org.jetbrains.java.decompiler.util.collections.SFormsFastMapDirect;

import java.util.*;
Expand Down Expand Up @@ -119,103 +111,129 @@ private static void setExprentVersionsToNull(Exprent exprent) {
}
}

record NodePath(DirectNode node, Map<VarVersionPair, Exprent> map, List<Node> nodes) {}

record Node(List<Exprent> exprents, Statement stat) {}

private static boolean iterateStatements(RootStatement root, SSAUConstructorSparseEx ssa, StackSimplifyOptions options) {
FlattenStatementsHelper flatthelper = new FlattenStatementsHelper();
DirectGraph dgraph = flatthelper.buildDirectGraph(root);

boolean res = false;
Map<VarVersionPair, LocalVariable> lvts = new HashMap<>();

Set<DirectNode> setVisited = new HashSet<>();
LinkedList<DirectNode> stack = new LinkedList<>();
LinkedList<Map<VarVersionPair, Exprent>> stackMaps = new LinkedList<>();
boolean res = iterateStatementsNested(root, dgraph.first, ssa, options, dgraph, lvts, new HashSet<>(Set.of(dgraph.first)), new LinkedList<>());

stack.add(dgraph.first);
stackMaps.add(new HashMap<>());
dgraph.iterateExprentsDeep(ex -> {
if (ex instanceof VarExprent var && lvts.containsKey(var.getVarVersionPair())) {
LocalVariable lvt = lvts.get(var.getVarVersionPair());
if (var.getLVT() == null) {
var.setLVT(lvt);
}
}

Map<VarVersionPair, LocalVariable> lvts = new HashMap<>();
return 0;
});

int[] ret = {0, 0};
while (!stack.isEmpty()) {
DirectNode nd = stack.removeFirst();
Map<VarVersionPair, Exprent> mapVarValues = stackMaps.removeFirst();
return res;
}

if (setVisited.contains(nd)) {
continue;
}
private static boolean iterateStatementsNested(Statement root, DirectNode start, SSAUConstructorSparseEx ssa, StackSimplifyOptions options, DirectGraph dgraph, Map<VarVersionPair, LocalVariable> lvts, Set<DirectNode> setVisited, List<DirectNode> exits) {
boolean res = false;

setVisited.add(nd);
LinkedList<NodePath> stack = new LinkedList<>();

List<List<Exprent>> lstLists = new ArrayList<>();
stack.add(new NodePath(start, new HashMap<>(), new ArrayList<>()));

int[] ret = {0, 0};
while (!stack.isEmpty()) {
NodePath path = stack.removeFirst();
DirectNode nd = path.node;
Map<VarVersionPair, Exprent> mapVarValues = path.map;

List<Node> lstLists = path.nodes;
if (!nd.exprents.isEmpty()) {
lstLists.add(nd.exprents);
lstLists.add(new Node(nd.exprents, nd.statement));
}

List<DirectEdge> succs = nd.getSuccessors(DirectEdgeType.REGULAR);
if (succs.size() == 1) {
DirectNode ndsucc = succs.get(0).getDestination();

if (ndsucc.type == DirectNodeType.TAIL && !ndsucc.exprents.isEmpty()) {
lstLists.add(succs.get(0).getDestination().exprents);
nd = ndsucc;
List<DirectNode> succs = nd.getSuccessors(DirectEdgeType.REGULAR).stream().map(DirectEdge::getDestination).toList();
List<DirectNode> newSuccs = new ArrayList<>();

for (DirectNode succ : succs) {
if (!setVisited.add(succ)) {
// Skip seen nodes
} else if (succ.statement instanceof SwitchStatement switchSt && switchSt.isPhantom() && root != switchSt) {
// Treat phantom switch statements as a sub method
List<DirectNode> subExits = new ArrayList<>();
res |= iterateStatementsNested(switchSt, succ, ssa, options, dgraph, lvts, setVisited, subExits);
newSuccs.addAll(subExits);
} else if (!root.containsStatement(succ.statement) && !(root instanceof RootStatement)) {
exits.add(succ);
} else {
newSuccs.add(succ);
}
}

// To handle stacks created by some duplicated bytecode (dup, dup_x2, etc.) better, we run the simplification algorithm in 2 passes.
// The first pass is the classic algorithm, and the second pass is a more aggressive one that allows some slightly unsafe operations, such as simplifying across variables.
// To ensure the second pass doesn't break good bytecode, if the first pass manages to update any exprent it will cancel the second pass.
// This behavior can be turned off with a fernflower preference.
for (int stackStage = 0; stackStage < 2; stackStage++) {
// If instructed to not use the second pass, set it to 2 here to prevent the loop from working
if (!DecompilerContext.getOption(IFernflowerPreferences.SIMPLIFY_STACK_SECOND_PASS)) {
stackStage = 2;
}
succs = newSuccs;

for (int i = 0; i < lstLists.size(); i++) {
List<Exprent> lst = lstLists.get(i);
if (succs.size() == 1) {
stack.add(new NodePath(succs.get(0), new HashMap<>(mapVarValues), new ArrayList<>(lstLists)));
} else {
// To handle stacks created by some duplicated bytecode (dup, dup_x2, etc.) better, we run the simplification algorithm in 2 passes.
// The first pass is the classic algorithm, and the second pass is a more aggressive one that allows some slightly unsafe operations, such as simplifying across variables.
// To ensure the second pass doesn't break good bytecode, if the first pass manages to update any exprent it will cancel the second pass.
// This behavior can be turned off with a fernflower preference.
for (int stackStage = 0; stackStage < 2; stackStage++) {
// If instructed to not use the second pass, set it to 2 here to prevent the loop from working
if (!DecompilerContext.getOption(IFernflowerPreferences.SIMPLIFY_STACK_SECOND_PASS)) {
stackStage = 2;
}

int index = 0;
while (index < lst.size()) {
Exprent next = null;
for (int i = 0; i < lstLists.size(); i++) {
Node node = lstLists.get(i);
List<Exprent> lst = node.exprents;

if (index == lst.size() - 1) {
if (i < lstLists.size() - 1) {
next = lstLists.get(i + 1).get(0);
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).exprents.get(0);
}
} else {
next = lst.get(index + 1);
}
} else {
next = lst.get(index + 1);
}

boolean simplifyAcrossStack = stackStage == 1;
boolean simplifyAcrossStack = stackStage == 1;

// {newIndex, changed}
iterateExprent(lst, nd.statement, lvts, index, next, mapVarValues, ssa, simplifyAcrossStack, ret, options);
// {newIndex, changed}
iterateExprent(lst, node.stat, lvts, index, next, mapVarValues, ssa, simplifyAcrossStack, ret, options);

// If index is specified, set to that
if (ret[0] >= 0) {
index = ret[0];
} else {
// Otherwise, continue to next index
index++;
}
// If index is specified, set to that
if (ret[0] >= 0) {
index = ret[0];
} else {
// Otherwise, continue to next index
index++;
}

// Mark if we changed
boolean changed = ret[1] == 1;
res |= changed;
// Mark if we changed
boolean changed = ret[1] == 1;
res |= changed;

// We only want to simplify across stack bounds if we were not able to change *anything*
if (changed) {
// Cancel the second pass by setting the stage to 2, preventing the check next time it runs
stackStage = 2;
// We only want to simplify across stack bounds if we were not able to change *anything*
if (changed) {
// Cancel the second pass by setting the stage to 2, preventing the check next time it runs
stackStage = 2;
}
// An (unintentional) side effect of this implementation is that as soon as the second pass is able to change the stack, it'll cancel further iteration of the second pass, preventing it from creating wrong code by accident.
}
// An (unintentional) side effect of this implementation is that as soon as the second pass is able to change the stack, it'll cancel further iteration of the second pass, preventing it from creating wrong code by accident.
}
}
}

for (DirectEdge ndx : succs) {
stack.add(ndx.getDestination());
stackMaps.add(new HashMap<>(mapVarValues));
for (DirectNode ndx : succs) {
stack.add(new NodePath(ndx, new HashMap<>(mapVarValues), new ArrayList<>()));
}
}

// make sure the 3 special exprent lists in a loop (init, condition, increment) are not empty
Expand All @@ -235,18 +253,6 @@ private static boolean iterateStatements(RootStatement root, SSAUConstructorSpar
}
}
}

dgraph.iterateExprentsDeep(ex -> {
if (ex instanceof VarExprent var && lvts.containsKey(var.getVarVersionPair())) {
LocalVariable lvt = lvts.get(var.getVarVersionPair());
if (var.getLVT() == null) {
var.setLVT(lvt);
}
}

return 0;
});

return res;
}

Expand Down Expand Up @@ -316,7 +322,11 @@ private static void iterateExprent(List<Exprent> lstExprents,
int changed = 0;

Object[] arr = {null, false, false};
for (Exprent expr : exprent.getAllExprents()) {
List<Exprent> containing = exprent.getAllExprents();
if (exprent instanceof SwitchExprent switchExp) {
containing = switchExp.getBacking().getHeadexprentList();
}
for (Exprent expr : containing) {
while (true) {
iterateChildExprent(expr, exprent, next, mapVarValues, ssau, arr, options);
Exprent retexpr = (Exprent)arr[0];
Expand Down Expand Up @@ -733,7 +743,7 @@ private static boolean areTreesEqual(VarExprent left, List<Exprent> treeA, List<
private static Set<VarVersionPair> getAllVersions(Exprent exprent) {
Set<VarVersionPair> res = new HashSet<>();

List<Exprent> exprents = exprent.getAllExprents(true);
List<Exprent> exprents = getAllExprents(exprent);
exprents.add(exprent);

for (Exprent expr : exprents) {
Expand All @@ -758,7 +768,11 @@ private static void iterateChildExprent(Exprent exprent,
boolean changed = false;

Object[] arr = {null, false, false};
for (Exprent expr : exprent.getAllExprents()) {
List<Exprent> containing = exprent.getAllExprents();
if (exprent instanceof SwitchExprent switchExp) {
containing = switchExp.getBacking().getHeadexprentList();
}
for (Exprent expr : containing) {
while (true) {
iterateChildExprent(expr, parent, next, mapVarValues, ssau, arr, options);
Exprent retexpr = (Exprent)arr[0];
Expand Down Expand Up @@ -810,7 +824,7 @@ private static void iterateChildExprent(Exprent exprent,
}

boolean isHeadSynchronized = false;
if (next == null && parent instanceof MonitorExprent) {
if (parent instanceof MonitorExprent) {
MonitorExprent monexpr = (MonitorExprent)parent;
if (monexpr.getMonType() == MonitorExprent.Type.ENTER && exprent.equals(monexpr.getValue())) {
isHeadSynchronized = true;
Expand Down Expand Up @@ -1003,7 +1017,7 @@ private static Map<Integer, Set<VarVersionPair>> getAllVarVersions(VarVersionPai
Map<Integer, Set<VarVersionPair>> map = new HashMap<>();
SFormsFastMapDirect mapLiveVars = ssau.getLiveVarVersionsMap(leftvar);

List<Exprent> lst = exprent.getAllExprents(true);
List<Exprent> lst = getAllExprents(exprent);
lst.add(exprent);

for (Exprent expr : lst) {
Expand Down Expand Up @@ -1209,6 +1223,16 @@ private static List<VarVersionNode> getRoots(VarVersionNode vvnode) {
return ret;
}

private static List<Exprent> getAllExprents(Exprent exprent) {
List<Exprent> exprents = exprent.getAllExprents(true);
for (int i = 0; i < exprents.size(); i++) {
if (exprents.get(i) instanceof SwitchExprent switchExp) {
exprents.addAll(getAllExprents(switchExp.getBacking().getHeadexprent()));
}
}
return exprents;
}

public static class StackSimplifyOptions {
private boolean inlineRegularVars = false;
public StackSimplifyOptions() {
Expand Down
2 changes: 2 additions & 0 deletions test/org/jetbrains/java/decompiler/SingleClassesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,8 @@ private void registerDefault() {
register(JAVA_17, "TestStringSwitchTypes");

register(JAVA_16, "TestSwitchExpressionIfBlocks");

register(JAVA_16, "TestSwitchExpressionMultiple");
}

private void registerEntireClassPath() {
Expand Down
Loading
Loading