fix: extract common switch break, remove unreachable (#2697)

This commit is contained in:
Skylot
2025-11-13 20:43:43 +00:00
parent 7ea478e18a
commit 6aeaf6aca9
16 changed files with 433 additions and 99 deletions
@@ -66,6 +66,7 @@ import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.SwitchBreakVisitor;
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
@@ -196,12 +197,14 @@ public class Jadx {
passes.add(new FixAccessModifiers());
passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor());
passes.add(new SwitchBreakVisitor());
if (args.isInlineMethods()) {
passes.add(new MarkMethodsForInline());
}
passes.add(new ProcessVariables());
passes.add(new ApplyVariableNames());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
@@ -25,6 +25,6 @@ public class RegionRefAttr implements IJadxAttribute {
@Override
public String toString() {
return "RegionRef:" + region;
return "RegionRef:" + region.baseString();
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.nodes;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AttrNode;
@@ -14,7 +14,9 @@ public final class InsnContainer extends AttrNode implements IBlock {
private final List<InsnNode> insns;
public InsnContainer(InsnNode insn) {
this.insns = Collections.singletonList(insn);
List<InsnNode> list = new ArrayList<>(1);
list.add(insn);
this.insns = list;
}
public InsnContainer(List<InsnNode> insns) {
@@ -28,11 +30,11 @@ public final class InsnContainer extends AttrNode implements IBlock {
@Override
public String baseString() {
return Integer.toString(insns.size());
return "IC";
}
@Override
public String toString() {
return "InsnContainer:" + insns.size();
return "InsnContainer";
}
}
@@ -217,6 +217,19 @@ public class InsnNode extends LineAttrNode {
}
}
public boolean isExitEdgeInsn() {
switch (getType()) {
case RETURN:
case THROW:
case CONTINUE:
case BREAK:
return true;
default:
return false;
}
}
public boolean canRemoveResult() {
switch (getType()) {
case INVOKE:
@@ -86,13 +86,13 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
@Override
public String baseString() {
return header.baseString();
return "SW:" + header.baseString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Switch: ").append(cases.size());
sb.append("Switch: ").append(header.baseString());
for (CaseInfo caseInfo : cases) {
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
@@ -12,7 +12,7 @@ public abstract class AbstractRegionVisitor implements IRegionVisitor {
}
@Override
public void processBlock(MethodNode mth, IBlock container) {
public void processBlock(MethodNode mth, IBlock block) {
}
@Override
@@ -1,20 +1,11 @@
package jadx.core.dex.visitors.regions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnContainer;
@@ -23,11 +14,9 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.utils.RegionUtils;
final class PostProcessRegions extends AbstractRegionVisitor {
private static final Logger LOG = LoggerFactory.getLogger(PostProcessRegions.class);
import jadx.core.dex.visitors.regions.maker.SwitchRegionMaker;
public final class PostProcessRegions extends AbstractRegionVisitor {
private static final IRegionVisitor INSTANCE = new PostProcessRegions();
static void process(MethodNode mth) {
@@ -41,8 +30,7 @@ final class PostProcessRegions extends AbstractRegionVisitor {
LoopRegion loop = (LoopRegion) region;
loop.mergePreCondition();
} else if (region instanceof SwitchRegion) {
// insert 'break' in switch cases (run after try/catch insertion)
processSwitch(mth, (SwitchRegion) region);
SwitchRegionMaker.insertBreaks(mth, (SwitchRegion) region);
} else if (region instanceof Region) {
insertEdgeInsn((Region) region);
}
@@ -76,55 +64,6 @@ final class PostProcessRegions extends AbstractRegionVisitor {
region.add(new InsnContainer(insns));
}
private static void processSwitch(MethodNode mth, SwitchRegion sw) {
for (IContainer c : sw.getBranches()) {
if (c instanceof Region) {
Set<IBlock> blocks = new HashSet<>();
RegionUtils.getAllRegionBlocks(c, blocks);
if (blocks.isEmpty()) {
addBreakToContainer((Region) c);
} else {
for (IBlock block : blocks) {
if (block instanceof BlockNode) {
addBreakForBlock(mth, c, blocks, (BlockNode) block);
}
}
}
}
}
}
private static void addBreakToContainer(Region c) {
if (RegionUtils.hasExitEdge(c)) {
return;
}
List<InsnNode> insns = new ArrayList<>(1);
insns.add(new InsnNode(InsnType.BREAK, 0));
c.add(new InsnContainer(insns));
}
private static void addBreakForBlock(MethodNode mth, IContainer c, Set<IBlock> blocks, BlockNode bn) {
for (BlockNode s : bn.getCleanSuccessors()) {
if (!blocks.contains(s)
&& !bn.contains(AFlag.ADDED_TO_REGION)
&& !s.contains(AFlag.FALL_THROUGH)) {
addBreak(mth, c, bn);
return;
}
}
}
private static void addBreak(MethodNode mth, IContainer c, BlockNode bn) {
IContainer blockContainer = RegionUtils.getBlockContainer(c, bn);
if (blockContainer instanceof Region) {
addBreakToContainer((Region) blockContainer);
} else if (c instanceof Region) {
addBreakToContainer((Region) c);
} else {
LOG.warn("Can't insert break, container: {}, block: {}, mth: {}", blockContainer, bn, mth);
}
}
private PostProcessRegions() {
// singleton
}
@@ -0,0 +1,231 @@
package jadx.core.dex.visitors.regions;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.RegionRefAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.regions.maker.SwitchRegionMaker;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxException;
import static jadx.core.dex.attributes.nodes.CodeFeaturesAttr.CodeFeature.SWITCH;
@JadxVisitor(
name = "SwitchBreakVisitor",
desc = "Optimize 'break' instruction: common code extract, remove unreachable",
runAfter = LoopRegionVisitor.class // can add 'continue' at case end
)
public class SwitchBreakVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (CodeFeaturesAttr.contains(mth, SWITCH)) {
DepthRegionTraversal.traverse(mth, new ExtractCommonBreak());
DepthRegionTraversal.traverse(mth, new RemoveUnreachableBreak());
}
}
private static final class ExtractCommonBreak extends BaseSwitchRegionVisitor {
@Override
public boolean switchVisitCondition(MethodNode mth, SwitchRegion switchRegion) {
return countBreaks(mth, switchRegion) > 1;
}
@Override
public void processRegion(MethodNode mth, IRegion region) {
if (region instanceof IBranchRegion) {
// if break in all branches extract to parent region
processBranchRegion(region);
}
}
private void processBranchRegion(IRegion region) {
IRegion parentRegion = region.getParent();
if (parentRegion.contains(AFlag.FALL_THROUGH)) {
// fallthrough case, can't extract break
return;
}
boolean dontAddCommonBreak = false;
IBlock lastParentBlock = RegionUtils.getLastBlock(parentRegion);
if (BlockUtils.containsExitInsn(lastParentBlock)) {
if (isBreakBlock(lastParentBlock)) {
// parent block already contains 'break'
dontAddCommonBreak = true;
} else {
// can't add 'break' after 'return', 'throw' or 'continue'
return;
}
}
List<IContainer> branches = ((IBranchRegion) region).getBranches();
boolean removeBranchBreaks = false;
boolean removeCommonBreak = true; // all branches contain exit insns, common break is unreachable
for (IContainer branch : branches) {
if (branch == null) {
removeCommonBreak = false;
continue;
}
IBlock lastBlock = RegionUtils.getLastBlock(branch);
InsnNode lastInsn = BlockUtils.getLastInsn(lastBlock);
if (lastInsn == null) {
return;
}
if (lastInsn.getType() == InsnType.BREAK) {
removeBranchBreaks = true;
removeCommonBreak = false;
} else if (!lastInsn.isExitEdgeInsn()) {
removeCommonBreak = false;
}
}
if (removeBranchBreaks) {
// common 'break' confirmed
for (IContainer branch : branches) {
if (branch == null) {
continue;
}
// remove breaks from all branches
IBlock lastBlock = RegionUtils.getLastBlock(branch);
if (lastBlock != null) {
removeBreak(lastBlock, branch);
}
}
if (!dontAddCommonBreak) {
addBreakRegion.add(parentRegion);
}
}
if (removeCommonBreak && lastParentBlock != null) {
removeBreak(lastParentBlock, parentRegion);
}
}
private int countBreaks(MethodNode mth, IRegion region) {
AtomicInteger count = new AtomicInteger(0);
RegionUtils.visitBlocks(mth, region, block -> {
if (isBreakBlock(block)) {
count.incrementAndGet();
}
});
return count.get();
}
}
private static final class RemoveUnreachableBreak extends BaseSwitchRegionVisitor {
@Override
public void processRegion(MethodNode mth, IRegion region) {
List<IContainer> subBlocks = region.getSubBlocks();
IContainer lastContainer = ListUtils.last(subBlocks);
if (lastContainer instanceof IBlock) {
IBlock block = (IBlock) lastContainer;
if (isBreakBlock(block) && isPrevInsnIsExit(block, subBlocks)) {
removeBreak(block, region);
}
}
}
private boolean isPrevInsnIsExit(IBlock breakBlock, List<IContainer> subBlocks) {
InsnNode prevInsn = null;
if (breakBlock.getInstructions().size() > 1) {
// check prev insn in same block
List<InsnNode> insns = breakBlock.getInstructions();
prevInsn = insns.get(insns.size() - 2);
} else if (subBlocks.size() > 1) {
IContainer prev = subBlocks.get(subBlocks.size() - 2);
if (prev instanceof IBlock) {
List<InsnNode> insns = ((IBlock) prev).getInstructions();
prevInsn = ListUtils.last(insns);
}
}
return prevInsn != null && prevInsn.isExitEdgeInsn();
}
}
private abstract static class BaseSwitchRegionVisitor extends AbstractRegionVisitor {
protected final Set<IRegion> addBreakRegion = new HashSet<>();
protected final Set<IContainer> cleanupSet = new HashSet<>();
protected SwitchRegion currentSwitch;
public abstract void processRegion(MethodNode mth, IRegion region);
public boolean switchVisitCondition(MethodNode mth, SwitchRegion switchRegion) {
return true;
}
@Override
public boolean enterRegion(MethodNode mth, IRegion region) {
if (region instanceof SwitchRegion) {
SwitchRegion switchRegion = (SwitchRegion) region;
this.currentSwitch = switchRegion;
return switchVisitCondition(mth, switchRegion);
}
if (currentSwitch == null) {
return true;
}
processRegion(mth, region);
return true;
}
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
if (region == currentSwitch) {
currentSwitch = null;
addBreakRegion.clear();
cleanupSet.clear();
return;
}
if (addBreakRegion.contains(region)) {
addBreakRegion.remove(region);
region.getSubBlocks().add(SwitchRegionMaker.buildBreakContainer(currentSwitch));
}
if (cleanupSet.contains(region)) {
cleanupSet.remove(region);
region.getSubBlocks().removeIf(r -> r.contains(AFlag.REMOVE));
}
}
protected boolean isBreakBlock(@Nullable IBlock block) {
if (block != null) {
InsnNode lastInsn = ListUtils.last(block.getInstructions());
if (lastInsn != null && lastInsn.getType() == InsnType.BREAK) {
RegionRefAttr regionRefAttr = lastInsn.get(AType.REGION_REF);
return regionRefAttr != null && regionRefAttr.getRegion() == currentSwitch;
}
}
return false;
}
protected void removeBreak(IBlock breakBlock, IContainer parentContainer) {
List<InsnNode> instructions = breakBlock.getInstructions();
InsnNode last = ListUtils.last(instructions);
if (last != null && last.getType() == InsnType.BREAK) {
ListUtils.removeLast(instructions);
if (instructions.isEmpty()) {
breakBlock.add(AFlag.REMOVE);
cleanupSet.add(parentContainer);
}
}
}
}
@Override
public String getName() {
return "SwitchBreakVisitor";
}
}
@@ -18,12 +18,12 @@ public abstract class TracedRegionVisitor implements IRegionVisitor {
}
@Override
public void processBlock(MethodNode mth, IBlock container) {
public void processBlock(MethodNode mth, IBlock block) {
IRegion curRegion = regionStack.peek();
processBlockTraced(mth, container, curRegion);
processBlockTraced(mth, block, curRegion);
}
public abstract void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion);
public abstract void processBlockTraced(MethodNode mth, IBlock block, IRegion parentRegion);
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
@@ -6,6 +6,7 @@ import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,7 +84,7 @@ final class RegionStack {
*
* @param exit boundary node, null will be ignored
*/
public void addExit(BlockNode exit) {
public void addExit(@Nullable BlockNode exit) {
if (exit != null) {
curState.exits.add(exit);
}
@@ -95,7 +96,7 @@ final class RegionStack {
}
}
public void removeExit(BlockNode exit) {
public void removeExit(@Nullable BlockNode exit) {
if (exit != null) {
curState.exits.remove(exit);
}
@@ -19,17 +19,24 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.dex.visitors.regions.SwitchBreakVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.blocks.BlockSet;
import jadx.core.utils.exceptions.JadxRuntimeException;
final class SwitchRegionMaker {
public final class SwitchRegionMaker {
private final MethodNode mth;
private final RegionMaker regionMaker;
@@ -61,21 +68,32 @@ final class SwitchRegionMaker {
BlockNode out = calcSwitchOut(block, insn, stack);
stack.addExit(out);
processFallThroughCases(sw, out, stack, blocksMap);
addCases(sw, out, stack, blocksMap);
removeEmptyCases(insn, sw, defCase);
stack.pop();
return out;
}
private void processFallThroughCases(SwitchRegion sw, @Nullable BlockNode out,
/**
* Insert 'break' for all cases in switch region
* Executed in {@link jadx.core.dex.visitors.regions.PostProcessRegions} after try/catch wrap to
* handle all blocks
*/
public static void insertBreaks(MethodNode mth, SwitchRegion sw) {
for (SwitchRegion.CaseInfo caseInfo : sw.getCases()) {
insertBreaksForCase(mth, sw, caseInfo.getContainer());
}
}
private void addCases(SwitchRegion sw, @Nullable BlockNode out,
RegionStack stack, Map<BlockNode, List<Object>> blocksMap) {
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
if (out != null) {
// detect fallthrough cases
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
caseBlocks.clear(out.getId());
for (BlockNode successor : sw.getHeader().getCleanSuccessors()) {
caseBlocks.clear(out.getPos());
for (BlockNode successor : sw.getHeader().getSuccessors()) {
BitSet df = successor.getDomFrontier();
if (df.intersects(caseBlocks)) {
BlockNode fallThroughBlock = getOneIntersectionBlock(out, caseBlocks, df);
@@ -93,31 +111,30 @@ final class SwitchRegionMaker {
}
}
}
for (Map.Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
List<Object> keysList = entry.getValue();
BlockNode caseBlock = entry.getKey();
Region caseRegion;
if (stack.containsExit(caseBlock)) {
sw.addCase(keysList, new Region(stack.peekRegion()));
caseRegion = new Region(stack.peekRegion());
} else {
BlockNode next = fallThroughCases.get(caseBlock);
stack.addExit(next);
Region caseRegion = regionMaker.makeRegion(caseBlock);
caseRegion = regionMaker.makeRegion(caseBlock);
stack.removeExit(next);
if (next != null) {
next.add(AFlag.FALL_THROUGH);
caseRegion.add(AFlag.FALL_THROUGH);
}
sw.addCase(keysList, caseRegion);
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
}
sw.addCase(keysList, caseRegion);
}
}
@Nullable
private BlockNode getOneIntersectionBlock(BlockNode out, BitSet caseBlocks, BitSet fallThroughSet) {
BitSet caseExits = BlockUtils.copyBlocksBitSet(mth, fallThroughSet);
caseExits.clear(out.getId());
caseExits.clear(out.getPos());
caseExits.and(caseBlocks);
return BlockUtils.bitSetToOneBlock(mth, caseExits);
}
@@ -341,4 +358,49 @@ final class SwitchRegionMaker {
}
return inserted;
}
/**
* Add break to every exit edge from 'case' region.
* 'Break' optimizations (code duplication, unreachable, etc.) will be done at
* {@link SwitchBreakVisitor}
*/
private static void insertBreaksForCase(MethodNode mth, SwitchRegion switchRegion, IContainer caseContainer) {
BlockSet caseBlocks = new BlockSet(mth);
RegionUtils.visitBlockNodes(mth, caseContainer, caseBlocks::add);
DepthRegionTraversal.traverse(mth, caseContainer, new AbstractRegionVisitor() {
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
boolean insertBreak = false;
if (region == caseContainer) {
// top region
insertBreak = true;
} else {
IContainer lastContainer = ListUtils.last(region.getSubBlocks());
if (lastContainer instanceof BlockNode) {
BlockNode lastBlock = (BlockNode) lastContainer;
for (BlockNode successor : lastBlock.getSuccessors()) {
if (!caseBlocks.contains(successor)) {
insertBreak = true;
break;
}
}
}
}
if (insertBreak && canAppendBreak(region)) {
region.getSubBlocks().add(buildBreakContainer(switchRegion));
}
}
});
}
public static boolean canAppendBreak(IRegion region) {
return !region.contains(AFlag.FALL_THROUGH) && !RegionUtils.hasExitBlock(region);
}
public static InsnContainer buildBreakContainer(SwitchRegion switchRegion) {
InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0);
breakInsn.add(AFlag.SYNTHETIC);
breakInsn.addAttr(new RegionRefAttr(switchRegion));
return new InsnContainer(breakInsn);
}
}
@@ -67,7 +67,10 @@ public class ListUtils {
return list.get(0);
}
public static <T> T last(List<T> list) {
public static <T> @Nullable T last(List<T> list) {
if (list == null || list.isEmpty()) {
return null;
}
return list.get(list.size() - 1);
}
@@ -51,9 +51,8 @@ public class RegionUtils {
return true;
}
if (container instanceof IRegion) {
IRegion region = (IRegion) container;
List<IContainer> blocks = region.getSubBlocks();
return !blocks.isEmpty() && hasExitEdge(blocks.get(blocks.size() - 1));
IContainer last = Utils.last(((IRegion) container).getSubBlocks());
return last != null && hasExitEdge(last);
}
throw new JadxRuntimeException(unknownContainerType(container));
}
@@ -81,6 +80,26 @@ public class RegionUtils {
}
}
public static @Nullable BlockNode getFirstBlockNode(IContainer container) {
if (container instanceof IBlock) {
if (container instanceof BlockNode) {
return (BlockNode) container;
}
return null;
}
if (container instanceof IBranchRegion) {
return null;
}
if (container instanceof IRegion) {
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
if (blocks.isEmpty()) {
return null;
}
return getFirstBlockNode(blocks.get(0));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
public static int getFirstSourceLine(IContainer container) {
if (container instanceof IBlock) {
return BlockUtils.getFirstSourceLine((IBlock) container);
@@ -517,6 +536,17 @@ public class RegionUtils {
});
}
public static void visitBlockNodes(MethodNode mth, IContainer container, Consumer<BlockNode> visitor) {
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
@Override
public void processBlock(MethodNode mth, IBlock block) {
if (block instanceof BlockNode) {
visitor.accept((BlockNode) block);
}
}
});
}
public static void visitRegions(MethodNode mth, IContainer container, Predicate<IRegion> visitor) {
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
@Override
@@ -57,10 +57,9 @@ public class TestSwitch2 extends IntegrationTest {
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
// .countString(4, "break;"
.countString(4, "break;")
// .countString(2, "return;")
// TODO: remove redundant reak and returns
.countString(5, "break;")
// TODO: remove redundant returns
.countString(4, "return;");
}
}
@@ -0,0 +1,53 @@
package jadx.tests.integration.switches;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.extensions.profiles.TestProfile;
import jadx.tests.api.extensions.profiles.TestWithProfiles;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestSwitchBreak2 extends IntegrationTest {
@SuppressWarnings("SwitchStatementWithTooFewBranches")
public static class TestCls {
private int value;
public void test(int i, boolean b1, boolean b2) {
setValue(-1);
switch (i) {
case 0:
if (b1 && b2) {
setValue(1);
// no break here;
} else {
setValue(2);
// no break here;
}
break;
default:
setValue(0);
break;
}
}
private void setValue(int value) {
this.value = value;
}
public void check() {
test(0, true, true);
assertThat(value).isEqualTo(1);
test(0, true, false);
assertThat(value).isEqualTo(2);
test(1, true, true);
assertThat(value).isEqualTo(0);
}
}
@TestWithProfiles({ TestProfile.JAVA11, TestProfile.D8_J11 })
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.countString(2, "break;");
}
}
@@ -60,9 +60,7 @@ public class TestSwitchWithTryCatch extends IntegrationTest {
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
// .countString(3, "break;")
.countString(4, "return;")
// TODO: remove redundant break
.countString(4, "break;");
.countString(3, "break;")
.countString(4, "return;");
}
}