提交 2107da2e 编写于 作者: S Skylot

fix: improve 'out' block detection in switch (#826)

上级 62ca30bb
......@@ -275,37 +275,41 @@ public class RegionGen extends InsnGen {
List<Object> keys = caseInfo.getKeys();
IContainer c = caseInfo.getContainer();
for (Object k : keys) {
code.startLine("case ");
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
code.startLine("default:");
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
code.startLine("case ");
addCaseKey(code, arg, k);
code.add(':');
}
code.add(':');
}
makeRegionIndent(code, c);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeRegionIndent(code, sw.getDefaultCase());
}
code.decIndent();
code.startLine('}');
return code;
}
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
}
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
code.startLine("try {");
makeRegionIndent(code, region.getTryRegion());
......
......@@ -623,7 +623,7 @@ public class InsnDecoder {
targets[i] = targets[i] - payloadOffset + offset;
}
int nextOffset = getNextInsnOffset(insnArr, offset);
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset, packed);
}
private InsnNode fillArray(DecodedInstruction insn) {
......
......@@ -16,20 +16,22 @@ public class SwitchNode extends TargetInsnNode {
private final Object[] keys;
private final int[] targets;
private final int def; // next instruction
private final boolean packed; // type of switch insn, if true can contain filler keys
private BlockNode[] targetBlocks;
private BlockNode defTargetBlock;
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
this(keys, targets, def);
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def, boolean packed) {
this(keys, targets, def, packed);
addArg(arg);
}
private SwitchNode(Object[] keys, int[] targets, int def) {
private SwitchNode(Object[] keys, int[] targets, int def, boolean packed) {
super(InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
this.def = def;
this.packed = packed;
}
public int getCasesCount() {
......@@ -48,6 +50,10 @@ public class SwitchNode extends TargetInsnNode {
return def;
}
public boolean isPacked() {
return packed;
}
public BlockNode[] getTargetBlocks() {
return targetBlocks;
}
......@@ -103,7 +109,7 @@ public class SwitchNode extends TargetInsnNode {
@Override
public InsnNode copy() {
SwitchNode copy = new SwitchNode(keys, targets, def);
SwitchNode copy = new SwitchNode(keys, targets, def, packed);
copy.targetBlocks = targetBlocks;
copy.defTargetBlock = defTargetBlock;
return copyCommonParams(copy);
......@@ -114,9 +120,13 @@ public class SwitchNode extends TargetInsnNode {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
for (int i = 0; i < targets.length; i++) {
sb.append(" case ").append(keys[i])
.append(": goto ").append(InsnUtils.formatOffset(targets[i]));
sb.append(CodeWriter.NL);
sb.append(" case ").append(keys[i]);
sb.append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append(CodeWriter.NL);
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
return sb.toString();
}
......
......@@ -13,10 +13,11 @@ import jadx.core.utils.Utils;
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
public static final Object DEFAULT_CASE_KEY = new Object();
private final BlockNode header;
private final List<CaseInfo> cases;
private IContainer defCase;
public SwitchRegion(IRegion parent, BlockNode header) {
super(parent);
......@@ -50,14 +51,6 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
cases.add(new CaseInfo(keysList, c));
}
public void setDefaultCase(IContainer block) {
defCase = block;
}
public IContainer getDefaultCase() {
return defCase;
}
public List<CaseInfo> getCases() {
return cases;
}
......@@ -68,23 +61,15 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<>(cases.size() + 2);
List<IContainer> all = new ArrayList<>(cases.size() + 1);
all.add(header);
all.addAll(getCaseContainers());
if (defCase != null) {
all.add(defCase);
}
return Collections.unmodifiableList(all);
}
@Override
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
branches.addAll(getCaseContainers());
if (defCase != null) {
branches.add(defCase);
}
return Collections.unmodifiableList(branches);
return Collections.unmodifiableList(getCaseContainers());
}
@Override
......@@ -97,13 +82,12 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
StringBuilder sb = new StringBuilder();
sb.append("Switch: ").append(cases.size());
for (CaseInfo caseInfo : cases) {
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
sb.append(CodeWriter.NL).append(" case ")
.append(Utils.listToString(caseInfo.getKeys()))
.append(Utils.listToString(keyStrings))
.append(" -> ").append(caseInfo.getContainer());
}
if (defCase != null) {
sb.append(CodeWriter.NL).append(" default -> ").append(defCase);
}
return sb.toString();
}
}
......@@ -27,6 +27,7 @@ public class DotGraphVisitor extends AbstractVisitor {
private static final String NL = "\\l";
private static final boolean PRINT_DOMINATORS = false;
private static final boolean PRINT_DOMINATORS_INFO = false;
private final boolean useRegions;
private final boolean rawInsn;
......@@ -182,6 +183,14 @@ public class DotGraphVisitor extends AbstractVisitor {
if (!attrs.isEmpty()) {
dot.add('|').add(attrs);
}
if (PRINT_DOMINATORS_INFO) {
dot.add('|');
dot.startLine("doms: ").add(escape(block.getDoms()));
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
dot.startLine("\\l");
}
String insns = insertInsns(mth, block);
if (!insns.isEmpty()) {
dot.add('|').add(insns);
......@@ -272,6 +281,13 @@ public class DotGraphVisitor extends AbstractVisitor {
}
}
private String escape(Object obj) {
if (obj == null) {
return "null";
}
return escape(obj.toString());
}
private String escape(String string) {
return string
.replace("\\", "") // TODO replace \"
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.regions;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
......@@ -10,6 +11,7 @@ import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -737,135 +739,71 @@ public class RegionMaker {
}
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) {
SwitchRegion sw = new SwitchRegion(currentRegion, block);
currentRegion.getSubBlocks().add(sw);
// map case blocks to keys
int len = insn.getTargets().length;
// sort by target
Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<>(len);
Object[] keysArr = insn.getKeys();
BlockNode[] targetBlocksArr = insn.getTargetBlocks();
for (int i = 0; i < len; i++) {
Object key = insn.getKeys()[i];
BlockNode targ = insn.getTargetBlocks()[i];
List<Object> keys = blocksMap.computeIfAbsent(targ, k -> new ArrayList<>(2));
keys.add(key);
List<Object> keys = blocksMap.computeIfAbsent(targetBlocksArr[i], k -> new ArrayList<>(2));
keys.add(keysArr[i]);
}
BlockNode defCase = insn.getDefTargetBlock();
if (defCase != null) {
blocksMap.remove(defCase);
List<Object> keys = blocksMap.computeIfAbsent(defCase, k -> new ArrayList<>(1));
keys.add(SwitchRegion.DEFAULT_CASE_KEY);
}
LoopInfo loop = mth.getLoopForBlock(block);
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
List<BlockNode> basicBlocks = mth.getBasicBlocks();
BitSet outs = new BitSet(basicBlocks.size());
outs.or(block.getDomFrontier());
for (BlockNode s : block.getCleanSuccessors()) {
BitSet df = s.getDomFrontier();
// fall through case block
if (df.cardinality() > 1) {
if (df.cardinality() > 2) {
LOG.debug("Unexpected case pattern, block: {}, mth: {}", s, mth);
} else {
BlockNode first = basicBlocks.get(df.nextSetBit(0));
BlockNode second = basicBlocks.get(df.nextSetBit(first.getId() + 1));
if (second.getDomFrontier().get(first.getId())) {
fallThroughCases.put(s, second);
df = new BitSet(df.size());
df.set(first.getId());
} else if (first.getDomFrontier().get(second.getId())) {
fallThroughCases.put(s, first);
df = new BitSet(df.size());
df.set(second.getId());
}
}
// search 'out' block - 'next' block after whole switch statement
BlockNode out;
LoopInfo loop = mth.getLoopForBlock(block);
if (loop == null) {
out = calcPostDomOut(mth, block, mth.getExitBlocks());
} else {
BlockNode loopEnd = loop.getEnd();
// treat 'continue' as exit
out = calcPostDomOut(mth, block, loopEnd.getPredecessors());
if (out != null) {
insertContinueInSwitch(block, out, loopEnd);
} else {
// no 'continue'
out = calcPostDomOut(mth, block, Collections.singletonList(loopEnd));
}
outs.or(df);
}
outs.clear(block.getId());
if (loop != null) {
outs.clear(loop.getStart().getId());
}
SwitchRegion sw = new SwitchRegion(currentRegion, block);
currentRegion.getSubBlocks().add(sw);
stack.push(sw);
stack.addExits(BlockUtils.bitSetToBlocks(mth, outs));
// check cases order if fall through case exists
if (!fallThroughCases.isEmpty()
&& isBadCasesOrder(blocksMap, fallThroughCases)) {
LOG.debug("Fixing incorrect switch cases order, method: {}", mth);
blocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
if (isBadCasesOrder(blocksMap, fallThroughCases)) {
mth.addWarn("Can't fix incorrect switch cases order");
}
}
stack.addExit(out);
// filter 'out' block
if (outs.cardinality() > 1) {
// remove exception handlers
BlockUtils.cleanBitSet(mth, outs);
}
if (outs.cardinality() > 1) {
// filter loop start and successors of other blocks
for (int i = outs.nextSetBit(0); i >= 0; i = outs.nextSetBit(i + 1)) {
BlockNode b = basicBlocks.get(i);
outs.andNot(b.getDomFrontier());
if (b.contains(AFlag.LOOP_START)) {
outs.clear(b.getId());
} else {
for (BlockNode s : b.getCleanSuccessors()) {
outs.clear(s.getId());
}
// detect fallthrough cases
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
if (out != null) {
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
caseBlocks.clear(out.getId());
for (BlockNode successor : block.getCleanSuccessors()) {
BlockNode fallThroughBlock = searchFallThroughCase(successor, out, caseBlocks);
if (fallThroughBlock != null) {
fallThroughCases.put(successor, fallThroughBlock);
}
}
}
if (loop != null && outs.cardinality() > 1) {
outs.clear(loop.getEnd().getId());
}
if (outs.cardinality() == 0) {
// one or several case blocks are empty,
// run expensive algorithm for find 'out' block
for (BlockNode maybeOut : block.getSuccessors()) {
boolean allReached = true;
for (BlockNode s : block.getSuccessors()) {
if (!isPathExists(s, maybeOut)) {
allReached = false;
break;
}
}
if (allReached) {
outs.set(maybeOut.getId());
break;
// check fallthrough cases order
if (!fallThroughCases.isEmpty() && isBadCasesOrder(blocksMap, fallThroughCases)) {
Map<BlockNode, List<Object>> newBlocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
if (isBadCasesOrder(newBlocksMap, fallThroughCases)) {
mth.addComment("JADX INFO: Can't fix incorrect switch cases order, some code will duplicate");
fallThroughCases.clear();
} else {
blocksMap = newBlocksMap;
}
}
}
BlockNode out = null;
if (outs.cardinality() == 1) {
out = basicBlocks.get(outs.nextSetBit(0));
stack.addExit(out);
} else if (loop == null && outs.cardinality() > 1) {
LOG.warn("Can't detect out node for switch block: {} in {}", block, mth);
}
if (loop != null) {
// check if 'continue' must be inserted
BlockNode end = loop.getEnd();
if (out != end && out != null) {
insertContinueInSwitch(block, out, end);
}
}
if (!stack.containsExit(defCase)) {
Region defRegion = makeRegion(defCase, stack);
if (RegionUtils.notEmpty(defRegion)) {
sw.setDefaultCase(defRegion);
}
}
for (Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
List<Object> keysList = entry.getValue();
BlockNode caseBlock = entry.getKey();
if (stack.containsExit(caseBlock)) {
// empty case block
sw.addCase(entry.getValue(), new Region(stack.peekRegion()));
sw.addCase(keysList, new Region(stack.peekRegion()));
} else {
BlockNode next = fallThroughCases.get(caseBlock);
stack.addExit(next);
......@@ -875,17 +813,100 @@ public class RegionMaker {
next.add(AFlag.FALL_THROUGH);
caseRegion.add(AFlag.FALL_THROUGH);
}
sw.addCase(entry.getValue(), caseRegion);
sw.addCase(keysList, caseRegion);
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
}
}
removeEmptyCases(insn, sw, defCase);
stack.pop();
return out;
}
private boolean isBadCasesOrder(Map<BlockNode, List<Object>> blocksMap,
Map<BlockNode, BlockNode> fallThroughCases) {
@Nullable
private BlockNode searchFallThroughCase(BlockNode successor, BlockNode out, BitSet caseBlocks) {
BitSet df = successor.getDomFrontier();
if (df.intersects(caseBlocks)) {
return getOneIntersectionBlock(out, caseBlocks, df);
}
Set<BlockNode> allPathsBlocks = BlockUtils.getAllPathsBlocks(successor, out);
Map<BlockNode, BitSet> bitSetMap = BlockUtils.calcPartialPostDominance(mth, allPathsBlocks, out);
BitSet pdoms = bitSetMap.get(successor);
if (pdoms != null && pdoms.intersects(caseBlocks)) {
return getOneIntersectionBlock(out, caseBlocks, pdoms);
}
return null;
}
@Nullable
private BlockNode getOneIntersectionBlock(BlockNode out, BitSet caseBlocks, BitSet fallThroughSet) {
BitSet caseExits = BlockUtils.copyBlocksBitSet(mth, fallThroughSet);
caseExits.clear(out.getId());
caseExits.and(caseBlocks);
return BlockUtils.bitSetToOneBlock(mth, caseExits);
}
@Nullable
private static BlockNode calcPostDomOut(MethodNode mth, BlockNode block, List<BlockNode> exits) {
if (exits.size() == 1 && mth.getExitBlocks().equals(exits)) {
// simple case: for only one exit which is equal to method exit block
return BlockUtils.calcImmediatePostDominator(mth, block);
}
// fast search: union of blocks dominance frontier
// work if no fallthrough cases and no returns inside switch
BitSet outs = BlockUtils.copyBlocksBitSet(mth, block.getDomFrontier());
for (BlockNode s : block.getCleanSuccessors()) {
outs.or(s.getDomFrontier());
}
outs.clear(block.getId());
if (outs.cardinality() != 1) {
// slow search: calculate partial post-dominance for every exit node
BitSet ipdoms = BlockUtils.newBlocksBitSet(mth);
for (BlockNode exitBlock : exits) {
Set<BlockNode> pathBlocks = BlockUtils.getAllPathsBlocks(block, exitBlock);
BlockNode ipdom = BlockUtils.calcPartialImmediatePostDominator(mth, block, pathBlocks, exitBlock);
if (ipdom != null) {
ipdoms.set(ipdom.getId());
}
}
outs.and(ipdoms);
}
return BlockUtils.bitSetToOneBlock(mth, outs);
}
/**
* Remove empty case blocks:
* 1. single 'default' case
* 2. filler cases if switch is 'packed' and 'default' case is empty
*/
private void removeEmptyCases(SwitchNode insn, SwitchRegion sw, BlockNode defCase) {
boolean defaultCaseIsEmpty;
if (defCase == null) {
defaultCaseIsEmpty = true;
} else {
defaultCaseIsEmpty = sw.getCases().stream()
.anyMatch(c -> c.getKeys().contains(SwitchRegion.DEFAULT_CASE_KEY)
&& RegionUtils.isEmpty(c.getContainer()));
}
if (defaultCaseIsEmpty) {
sw.getCases().removeIf(caseInfo -> {
if (RegionUtils.isEmpty(caseInfo.getContainer())) {
List<Object> keys = caseInfo.getKeys();
if (keys.contains(SwitchRegion.DEFAULT_CASE_KEY)) {
return true;
}
if (insn.isPacked()) {
return true;
}
}
return false;
});
}
}
private boolean isBadCasesOrder(Map<BlockNode, List<Object>> blocksMap, Map<BlockNode, BlockNode> fallThroughCases) {
BlockNode nextCaseBlock = null;
for (BlockNode caseBlock : blocksMap.keySet()) {
if (nextCaseBlock != null && !caseBlock.equals(nextCaseBlock)) {
......
......@@ -4,9 +4,11 @@ import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
......@@ -285,7 +287,19 @@ public class BlockUtils {
return null;
}
public static BitSet blocksToBitSet(MethodNode mth, List<BlockNode> blocks) {
public static BitSet newBlocksBitSet(MethodNode mth) {
return new BitSet(mth.getBasicBlocks().size());
}
public static BitSet copyBlocksBitSet(MethodNode mth, BitSet bitSet) {
BitSet copy = new BitSet(mth.getBasicBlocks().size());
if (!bitSet.isEmpty()) {
copy.or(bitSet);
}
return copy;
}
public static BitSet blocksToBitSet(MethodNode mth, Collection<BlockNode> blocks) {
BitSet bs = new BitSet(mth.getBasicBlocks().size());
for (BlockNode block : blocks) {
bs.set(block.getId());
......@@ -293,8 +307,16 @@ public class BlockUtils {
return bs;
}
@Nullable
public static BlockNode bitSetToOneBlock(MethodNode mth, BitSet bs) {
if (bs == null || bs.cardinality() != 1) {
return null;
}
return mth.getBasicBlocks().get(bs.nextSetBit(0));
}
public static List<BlockNode> bitSetToBlocks(MethodNode mth, BitSet bs) {
if (bs == null) {
if (bs == null || bs == EmptyBitSet.EMPTY) {
return Collections.emptyList();
}
int size = bs.cardinality();
......@@ -649,4 +671,102 @@ public class BlockUtils {
}
return false;
}
public static Map<BlockNode, BitSet> calcPostDominance(MethodNode mth) {
return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getExitBlocks().get(0));
}
public static Map<BlockNode, BitSet> calcPartialPostDominance(MethodNode mth, Collection<BlockNode> blockNodes, BlockNode exitBlock) {
int blocksCount = mth.getBasicBlocks().size();
Map<BlockNode, BitSet> map = new HashMap<>(blocksCount);
BitSet initSet = new BitSet(blocksCount);
for (BlockNode block : blockNodes) {
initSet.set(block.getId());
}
for (BlockNode block : blockNodes) {
BitSet postDoms = new BitSet(blocksCount);
postDoms.or(initSet);
map.put(block, postDoms);
}
BitSet exitBitSet = map.get(exitBlock);
exitBitSet.clear();
exitBitSet.set(exitBlock.getId());
BitSet domSet = new BitSet(blocksCount);
boolean changed;
do {
changed = false;
for (BlockNode block : blockNodes) {
if (block == exitBlock) {
continue;
}
BitSet d = map.get(block);
if (!changed) {
domSet.clear();
domSet.or(d);
}
for (BlockNode scc : block.getSuccessors()) {
BitSet scPDoms = map.get(scc);
if (scPDoms != null) {
d.and(scPDoms);
}
}
d.set(block.getId());
if (!changed && !d.equals(domSet)) {
changed = true;
map.put(block, d);
}
}
} while (changed);
blockNodes.forEach(block -> {
BitSet postDoms = map.get(block);
postDoms.clear(block.getId());
if (postDoms.isEmpty()) {
map.put(block, EmptyBitSet.EMPTY);
}
});
return map;
}
@Nullable
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block) {
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
if (oneSuccessor != null) {
return oneSuccessor;
}
return calcImmediatePostDominator(mth, block, calcPostDominance(mth));
}
@Nullable
public static BlockNode calcPartialImmediatePostDominator(MethodNode mth, BlockNode block,
Collection<BlockNode> blockNodes, BlockNode exitBlock) {
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
if (oneSuccessor != null) {
return oneSuccessor;
}
Map<BlockNode, BitSet> pDomsMap = calcPartialPostDominance(mth, blockNodes, exitBlock);
return calcImmediatePostDominator(mth, block, pDomsMap);
}
@Nullable
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block, Map<BlockNode, BitSet> postDomsMap) {
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
if (oneSuccessor != null) {
return oneSuccessor;
}
List<BlockNode> basicBlocks = mth.getBasicBlocks();
BitSet postDoms = postDomsMap.get(block);
BitSet bs = copyBlocksBitSet(mth, postDoms);
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
BlockNode pdomBlock = basicBlocks.get(i);
BitSet pdoms = postDomsMap.get(pdomBlock);
if (pdoms != null) {
bs.andNot(pdoms);
}
}
return bitSetToOneBlock(mth, bs);
}
}
......@@ -5,6 +5,7 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
......@@ -159,4 +160,11 @@ public class DebugUtils {
cw.startLine(indent).add("|+ ").add(it.next());
}
}
public static void printMap(Map<?, ?> map, String desc) {
LOG.debug("Map {} (size = {}):", desc, map.size());
for (Map.Entry<?, ?> entry : map.entrySet()) {
LOG.debug(" {}: {}", entry.getKey(), entry.getValue());
}
}
}
......@@ -76,7 +76,12 @@ public final class ImmutableList<E> implements List<E>, RandomAccess {
@Override
public boolean containsAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
for (Object obj : c) {
if (!contains(obj)) {
return false;
}
}
return true;
}
@NotNull
......
......@@ -10,6 +10,10 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
super(code, JadxCodeAssertions.class);
}
public JadxCodeAssertions containsOne(String substring) {
return countString(1, substring);
}
public JadxCodeAssertions countString(int count, String substring) {
isNotNull();
int actualCount = TestUtils.count(actual, substring);
......
......@@ -17,7 +17,7 @@ public class TestSwitch2 extends IntegrationTest {
boolean isScrolling;
float multiTouchZoomOldDist;
void test(int action) {
public void test(int action) {
switch (action & 255) {
case 0:
this.isLongtouchable = true;
......
......@@ -2,7 +2,6 @@ package jadx.tests.integration.switches;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
......@@ -12,6 +11,7 @@ public class TestSwitchFallThrough extends IntegrationTest {
public static class TestCls {
public int r;
@SuppressWarnings("fallthrough")
public void test(int a) {
int i = 10;
switch (a) {
......@@ -43,12 +43,14 @@ public class TestSwitchFallThrough extends IntegrationTest {
}
}
@NotYetImplemented("switch fallthrough")
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOnlyOnce("switch");
.containsOne("switch (a) {")
.containsOne("r = i;")
.containsOne("r = -1;")
.countString(2, "break;");
// code correctness checks done in 'check' method
}
}
......@@ -47,6 +47,8 @@ public class TestSwitchReturnFromCase extends IntegrationTest {
String code = cls.getCode().toString();
assertThat(code, containsString("switch (a % 10) {"));
// case 5: removed
assertEquals(5, count(code, "case "));
assertEquals(3, count(code, "break;"));
......
......@@ -26,6 +26,7 @@ public class TestSwitchWithFallThroughCase extends IntegrationTest {
}
break;
}
// fallthrough
case 2:
if (b) {
str += "2";
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册