core: don't inline variables defined in 'try' and used in 'catch'
This commit is contained in:
@@ -57,13 +57,13 @@ public class Jadx {
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new DebugInfoVisitor());
|
||||
passes.add(new TypeInference());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false, true));
|
||||
}
|
||||
|
||||
passes.add(new ConstInlinerVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new EliminatePhiNodes());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false, true));
|
||||
}
|
||||
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
|
||||
@@ -442,6 +442,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return exceptionHandlers.isEmpty();
|
||||
}
|
||||
|
||||
public int getExceptionHandlersCount() {
|
||||
return exceptionHandlers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if exists method with same name and arguments count
|
||||
*/
|
||||
|
||||
@@ -8,9 +8,11 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@@ -24,35 +26,52 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<InsnNode> toRemove = new ArrayList<InsnNode>();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
InstructionRemover remover = new InstructionRemover(mth, block);
|
||||
toRemove.clear();
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (checkInsn(mth, block, insn)) {
|
||||
remover.add(insn);
|
||||
toRemove.add(insn);
|
||||
}
|
||||
}
|
||||
remover.perform();
|
||||
if (!toRemove.isEmpty()) {
|
||||
InstructionRemover.removeAll(mth, block, toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkInsn(MethodNode mth, BlockNode block, InsnNode insn) {
|
||||
if (insn.getType() == InsnType.CONST) {
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (arg.isLiteral()) {
|
||||
ArgType resType = insn.getResult().getType();
|
||||
// make sure arg has correct type
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
arg.merge(resType);
|
||||
if (insn.getType() != InsnType.CONST) {
|
||||
return false;
|
||||
}
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (!arg.isLiteral()) {
|
||||
return false;
|
||||
}
|
||||
SSAVar sVar = insn.getResult().getSVar();
|
||||
if (mth.getExceptionHandlersCount() != 0) {
|
||||
for (RegisterArg useArg : sVar.getUseList()) {
|
||||
InsnNode parentInsn = useArg.getParentInsn();
|
||||
if (parentInsn != null) {
|
||||
// TODO: speed up expensive operations
|
||||
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
|
||||
if (!BlockUtils.isCleanPathExists(block, useBlock)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
return replaceConst(mth, insn, lit);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
ArgType resType = insn.getResult().getType();
|
||||
// make sure arg has correct type
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
arg.merge(resType);
|
||||
}
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
return replaceConst(mth, sVar, lit);
|
||||
}
|
||||
|
||||
private static boolean replaceConst(MethodNode mth, InsnNode insn, long literal) {
|
||||
List<RegisterArg> use = new ArrayList<RegisterArg>(insn.getResult().getSVar().getUseList());
|
||||
private static boolean replaceConst(MethodNode mth, SSAVar sVar, long literal) {
|
||||
List<RegisterArg> use = new ArrayList<RegisterArg>(sVar.getUseList());
|
||||
int replaceCount = 0;
|
||||
for (RegisterArg arg : use) {
|
||||
// if (arg.getSVar().isUsedInPhi()) {
|
||||
|
||||
@@ -216,6 +216,29 @@ public class BlockUtils {
|
||||
return traverseSuccessorsUntil(start, end, new BitSet());
|
||||
}
|
||||
|
||||
public static boolean isCleanPathExists(BlockNode start, BlockNode end) {
|
||||
if (start == end || start.getCleanSuccessors().contains(end)) {
|
||||
return true;
|
||||
}
|
||||
return traverseCleanSuccessorsUntil(start, end, new BitSet());
|
||||
}
|
||||
|
||||
private static boolean traverseCleanSuccessorsUntil(BlockNode from, BlockNode until, BitSet visited) {
|
||||
for (BlockNode s : from.getCleanSuccessors()) {
|
||||
if (s == until) {
|
||||
return true;
|
||||
}
|
||||
int id = s.getId();
|
||||
if (!visited.get(id) && !s.contains(AType.EXC_HANDLER)) {
|
||||
visited.set(id);
|
||||
if (traverseSuccessorsUntil(s, until, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) {
|
||||
if (start == end) {
|
||||
return true;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class InstructionRemover {
|
||||
if (toRemove.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
removeAll(insns, toRemove);
|
||||
removeAll(mth, insns, toRemove);
|
||||
toRemove.clear();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class InstructionRemover {
|
||||
|
||||
// Don't use 'insns.removeAll(toRemove)' because it will remove instructions by content
|
||||
// and here can be several instructions with same content
|
||||
private void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
|
||||
private static void removeAll(MethodNode mth, List<InsnNode> insns, List<InsnNode> toRemove) {
|
||||
for (InsnNode rem : toRemove) {
|
||||
unbindInsn(mth, rem);
|
||||
int insnsCount = insns.size();
|
||||
@@ -119,6 +119,10 @@ public class InstructionRemover {
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeAll(MethodNode mth, BlockNode block, List<InsnNode> insns) {
|
||||
removeAll(mth, block.getInstructions(), insns);
|
||||
}
|
||||
|
||||
public static void remove(MethodNode mth, BlockNode block, int index) {
|
||||
List<InsnNode> instructions = block.getInstructions();
|
||||
unbindInsn(mth, instructions.get(index));
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.junit.Assert.fail;
|
||||
public abstract class InternalJadxTest extends TestUtils {
|
||||
|
||||
protected boolean outputCFG = false;
|
||||
protected boolean isFallback = false;
|
||||
protected boolean deleteTmpJar = true;
|
||||
|
||||
protected String outDir = "test-out-tmp";
|
||||
@@ -76,6 +77,11 @@ public abstract class InternalJadxTest extends TestUtils {
|
||||
public boolean isRawCFGOutput() {
|
||||
return outputCFG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return isFallback;
|
||||
}
|
||||
}, new File(outDir));
|
||||
}
|
||||
|
||||
@@ -136,6 +142,12 @@ public abstract class InternalJadxTest extends TestUtils {
|
||||
this.outputCFG = true;
|
||||
}
|
||||
|
||||
// Use only for debug purpose
|
||||
@Deprecated
|
||||
protected void setFallback() {
|
||||
this.isFallback = true;
|
||||
}
|
||||
|
||||
// Use only for debug purpose
|
||||
@Deprecated
|
||||
protected void notDeleteTmpJar() {
|
||||
|
||||
@@ -41,7 +41,6 @@ public class TestIfInTry extends InternalJadxTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
setOutputCFG();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package jadx.tests.internal.trycatch;
|
||||
|
||||
import jadx.api.InternalJadxTest;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static jadx.tests.utils.JadxMatchers.containsOne;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestInlineInCatch extends InternalJadxTest {
|
||||
|
||||
public static class TestCls {
|
||||
private File dir;
|
||||
|
||||
public int test() {
|
||||
File output = null;
|
||||
try {
|
||||
output = File.createTempFile("f", "a", dir);
|
||||
return 0;
|
||||
} catch (Exception e) {
|
||||
if (output != null) {
|
||||
output.delete();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
assertThat(code, containsOne("File output = null;"));
|
||||
assertThat(code, containsOne("output = File.createTempFile(\"f\", \"a\", "));
|
||||
assertThat(code, containsOne("return 0;"));
|
||||
assertThat(code, containsOne("} catch (Exception e) {"));
|
||||
assertThat(code, containsOne("if (output != null) {"));
|
||||
assertThat(code, containsOne("output.delete();"));
|
||||
assertThat(code, containsOne("return 2;"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user