未验证 提交 e6b6b93c 编写于 作者: S Skylot

fix: improve blocks tree compare for finally extract (#1501)

上级 fcd58ae7
...@@ -7,6 +7,7 @@ public class Consts { ...@@ -7,6 +7,7 @@ public class Consts {
public static final boolean DEBUG_TYPE_INFERENCE = false; public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false; public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final boolean DEBUG_EXC_HANDLERS = false; public static final boolean DEBUG_EXC_HANDLERS = false;
public static final boolean DEBUG_FINALLY = false;
public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String"; public static final String CLASS_STRING = "java.lang.String";
......
...@@ -170,6 +170,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc ...@@ -170,6 +170,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
return contains(AFlag.RETURN); return contains(AFlag.RETURN);
} }
public boolean isEmpty() {
return instructions.isEmpty();
}
@Override @Override
public int hashCode() { public int hashCode() {
return startOffset; return startOffset;
......
...@@ -6,9 +6,12 @@ import java.util.List; ...@@ -6,9 +6,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.Utils;
public class FinallyExtractInfo { public class FinallyExtractInfo {
private final MethodNode mth;
private final ExceptionHandler finallyHandler; private final ExceptionHandler finallyHandler;
private final List<BlockNode> allHandlerBlocks; private final List<BlockNode> allHandlerBlocks;
private final List<InsnsSlice> duplicateSlices = new ArrayList<>(); private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
...@@ -16,12 +19,17 @@ public class FinallyExtractInfo { ...@@ -16,12 +19,17 @@ public class FinallyExtractInfo {
private final InsnsSlice finallyInsnsSlice = new InsnsSlice(); private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
private final BlockNode startBlock; private final BlockNode startBlock;
public FinallyExtractInfo(ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) { public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
this.mth = mth;
this.finallyHandler = finallyHandler; this.finallyHandler = finallyHandler;
this.startBlock = startBlock; this.startBlock = startBlock;
this.allHandlerBlocks = allHandlerBlocks; this.allHandlerBlocks = allHandlerBlocks;
} }
public MethodNode getMth() {
return mth;
}
public ExceptionHandler getFinallyHandler() { public ExceptionHandler getFinallyHandler() {
return finallyHandler; return finallyHandler;
} }
...@@ -45,4 +53,12 @@ public class FinallyExtractInfo { ...@@ -45,4 +53,12 @@ public class FinallyExtractInfo {
public BlockNode getStartBlock() { public BlockNode getStartBlock() {
return startBlock; return startBlock;
} }
@Override
public String toString() {
return "FinallyExtractInfo{"
+ "\n finally:\n " + finallyInsnsSlice
+ "\n dups:\n " + Utils.listToString(duplicateSlices, "\n ")
+ "\n}";
}
} }
...@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; ...@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
...@@ -38,6 +39,7 @@ import jadx.core.utils.Utils; ...@@ -38,6 +39,7 @@ import jadx.core.utils.Utils;
) )
public class MarkFinallyVisitor extends AbstractVisitor { public class MarkFinallyVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class); private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
// private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
@Override @Override
public void visit(MethodNode mth) { public void visit(MethodNode mth) {
...@@ -60,7 +62,7 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -60,7 +62,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
} }
} }
} catch (Exception e) { } catch (Exception e) {
LOG.warn("Undo finally extract visitor, mth: {}", mth, e); mth.addWarnComment("Undo finally extract visitor", e);
undoFinallyVisitor(mth); undoFinallyVisitor(mth);
} }
} }
...@@ -100,20 +102,23 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -100,20 +102,23 @@ public class MarkFinallyVisitor extends AbstractVisitor {
List<BlockNode> handlerBlocks = List<BlockNode> handlerBlocks =
new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock)); new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock));
handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception' handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception'
handlerBlocks.removeIf(b -> BlockUtils.checkLastInsnType(b, InsnType.THROW)); cutPathEnds(mth, handlerBlocks);
if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) { if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) {
// remove empty catch // remove empty catch
allHandler.getTryBlock().removeHandler(allHandler); allHandler.getTryBlock().removeHandler(allHandler);
return true; return true;
} }
BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors()); BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors());
FinallyExtractInfo extractInfo = new FinallyExtractInfo(allHandler, startBlock, handlerBlocks); FinallyExtractInfo extractInfo = new FinallyExtractInfo(mth, allHandler, startBlock, handlerBlocks);
if (Consts.DEBUG_FINALLY) {
LOG.debug("Finally info: handler=({}), start={}, blocks={}", allHandler, startBlock, handlerBlocks);
}
boolean hasInnerBlocks = !tryBlock.getInnerTryBlocks().isEmpty(); boolean hasInnerBlocks = !tryBlock.getInnerTryBlocks().isEmpty();
List<ExceptionHandler> handlers; List<ExceptionHandler> handlers;
if (hasInnerBlocks) { if (hasInnerBlocks) {
// collect handlers from this and all inner blocks (intentionally not using recursive collect for // collect handlers from this and all inner blocks
// now) // (intentionally not using recursive collect for now)
handlers = new ArrayList<>(tryBlock.getHandlers()); handlers = new ArrayList<>(tryBlock.getHandlers());
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) { for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
handlers.addAll(innerTryBlock.getHandlers()); handlers.addAll(innerTryBlock.getHandlers());
...@@ -137,10 +142,12 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -137,10 +142,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
} }
} }
} }
if (Consts.DEBUG_FINALLY) {
LOG.debug("Handlers slices:\n{}", extractInfo);
}
boolean mergeInnerTryBlocks; boolean mergeInnerTryBlocks;
int duplicatesCount = extractInfo.getDuplicateSlices().size(); int duplicatesCount = extractInfo.getDuplicateSlices().size();
boolean fullTryBlock = duplicatesCount == (handlers.size() - 1); if (duplicatesCount == (handlers.size() - 1)) {
if (fullTryBlock) {
// all collected handlers have duplicate block // all collected handlers have duplicate block
mergeInnerTryBlocks = hasInnerBlocks; mergeInnerTryBlocks = hasInnerBlocks;
} else { } else {
...@@ -170,15 +177,24 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -170,15 +177,24 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (upPath.size() < handlerBlocks.size()) { if (upPath.size() < handlerBlocks.size()) {
continue; continue;
} }
if (Consts.DEBUG_FINALLY) {
LOG.debug("Checking dup path starts: {} from {}", upPath, pred);
}
for (BlockNode block : upPath) { for (BlockNode block : upPath) {
if (searchDuplicateInsns(block, extractInfo)) { if (searchDuplicateInsns(block, extractInfo)) {
found = true; found = true;
if (Consts.DEBUG_FINALLY) {
LOG.debug("Found dup in: {} from {}", block, pred);
}
break; break;
} else { } else {
extractInfo.getFinallyInsnsSlice().resetIncomplete(); extractInfo.getFinallyInsnsSlice().resetIncomplete();
} }
} }
} }
if (Consts.DEBUG_FINALLY) {
LOG.debug("Result slices:\n{}", extractInfo);
}
if (!found) { if (!found) {
return false; return false;
} }
...@@ -204,6 +220,28 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -204,6 +220,28 @@ public class MarkFinallyVisitor extends AbstractVisitor {
return true; return true;
} }
private static void cutPathEnds(MethodNode mth, List<BlockNode> handlerBlocks) {
List<BlockNode> throwBlocks = ListUtils.filter(handlerBlocks,
b -> BlockUtils.checkLastInsnType(b, InsnType.THROW));
if (throwBlocks.size() != 1) {
mth.addDebugComment("Finally have unexpected throw blocks count: " + throwBlocks.size() + ", expect 1");
return;
}
BlockNode throwBlock = throwBlocks.get(0);
handlerBlocks.remove(throwBlock);
removeEmptyUpPath(handlerBlocks, throwBlock);
}
private static void removeEmptyUpPath(List<BlockNode> handlerBlocks, BlockNode startBlock) {
for (BlockNode pred : startBlock.getPredecessors()) {
if (pred.isEmpty()) {
if (handlerBlocks.remove(pred) && !BlockUtils.isBackEdge(pred, startBlock)) {
removeEmptyUpPath(handlerBlocks, pred);
}
}
}
}
private static List<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) { private static List<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) {
Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock); Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock);
if (bottom == mth.getExitBlock()) { if (bottom == mth.getExitBlock()) {
...@@ -219,9 +257,8 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -219,9 +257,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) { for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
List<InsnNode> dupInsnsList = dupSlice.getInsnsList(); List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
if (dupInsnsList.size() != finallyInsnsList.size()) { if (dupInsnsList.size() != finallyInsnsList.size()) {
if (LOG.isDebugEnabled()) { extractInfo.getMth().addDebugComment(
LOG.debug("Incorrect finally slice size: {}, expected: {}", dupSlice, finallySlice); "Incorrect finally slice size: " + dupSlice + ", expected: " + finallySlice);
}
return false; return false;
} }
} }
...@@ -231,9 +268,8 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -231,9 +268,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
List<InsnNode> insnsList = dupSlice.getInsnsList(); List<InsnNode> insnsList = dupSlice.getInsnsList();
InsnNode dupInsn = insnsList.get(i); InsnNode dupInsn = insnsList.get(i);
if (finallyInsn.getType() != dupInsn.getType()) { if (finallyInsn.getType() != dupInsn.getType()) {
if (LOG.isDebugEnabled()) { extractInfo.getMth().addDebugComment(
LOG.debug("Incorrect finally slice insn: {}, expected: {}", dupInsn, finallyInsn); "Incorrect finally slice insn: " + dupInsn + ", expected: " + finallyInsn);
}
return false; return false;
} }
} }
...@@ -342,24 +378,29 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -342,24 +378,29 @@ public class MarkFinallyVisitor extends AbstractVisitor {
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) { private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
List<InsnNode> dupInsns = dupBlock.getInstructions(); List<InsnNode> dupInsns = dupBlock.getInstructions();
List<InsnNode> finallyInsns = finallyBlock.getInstructions(); List<InsnNode> finallyInsns = finallyBlock.getInstructions();
if (dupInsns.size() < finallyInsns.size()) { int dupSize = dupInsns.size();
int finSize = finallyInsns.size();
if (dupSize < finSize) {
return null; return null;
} }
int startPos = dupInsns.size() - finallyInsns.size(); int startPos;
int endPos = 0; int endPos = 0;
// fast check from end of block if (dupSize == finSize) {
if (!checkInsns(dupInsns, finallyInsns, startPos)) { if (!checkInsns(dupInsns, finallyInsns, 0)) {
// check from block start return null;
if (checkInsns(dupInsns, finallyInsns, 0)) { }
startPos = 0; startPos = 0;
endPos = finallyInsns.size(); } else {
} else { // dupSize > finSize
startPos = dupSize - finSize;
// fast check from end of block
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
// search start insn // search start insn
boolean found = false; boolean found = false;
for (int i = 1; i < startPos; i++) { for (int i = 1; i < startPos; i++) {
if (checkInsns(dupInsns, finallyInsns, i)) { if (checkInsns(dupInsns, finallyInsns, i)) {
startPos = i; startPos = i;
endPos = finallyInsns.size() + i; endPos = finSize + i;
found = true; found = true;
break; break;
} }
...@@ -379,7 +420,7 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -379,7 +420,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// both slices completed // both slices completed
complete = true; complete = true;
} else { } else {
endIndex = dupInsns.size(); endIndex = dupSize;
complete = false; complete = false;
} }
...@@ -393,9 +434,8 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -393,9 +434,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (finallySlice.isComplete()) { if (finallySlice.isComplete()) {
// compare slices // compare slices
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) { if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
if (LOG.isDebugEnabled()) { extractInfo.getMth().addDebugComment(
LOG.debug("Another duplicated slice has different insns count: {}, finally: {}", slice, finallySlice); "Another duplicated slice has different insns count: " + slice + ", finally: " + finallySlice);
}
return null; return null;
} }
// TODO: add additional slices checks // TODO: add additional slices checks
...@@ -522,7 +562,7 @@ public class MarkFinallyVisitor extends AbstractVisitor { ...@@ -522,7 +562,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
DepthTraversal.visit(visitor, mth); DepthTraversal.visit(visitor, mth);
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Undo finally extract failed, mth: {}", mth, e); mth.addError("Undo finally extract failed", e);
} }
} }
} }
...@@ -79,8 +79,6 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -79,8 +79,6 @@ public abstract class IntegrationTest extends TestUtils {
private static final String TEST_DIRECTORY = "src/test/java"; private static final String TEST_DIRECTORY = "src/test/java";
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY; private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
private static final String OUT_DIR = "test-out-tmp";
private static final String DEFAULT_INPUT_PLUGIN = "dx"; private static final String DEFAULT_INPUT_PLUGIN = "dx";
/** /**
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests * Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
...@@ -132,7 +130,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -132,7 +130,7 @@ public abstract class IntegrationTest extends TestUtils {
this.useJavaInput = null; this.useJavaInput = null;
args = new JadxArgs(); args = new JadxArgs();
args.setOutDir(new File(OUT_DIR)); args.setOutDir(new File("test-out-tmp"));
args.setShowInconsistentCode(true); args.setShowInconsistentCode(true);
args.setThreadsCount(1); args.setThreadsCount(1);
args.setSkipResources(true); args.setSkipResources(true);
...@@ -156,6 +154,10 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -156,6 +154,10 @@ public abstract class IntegrationTest extends TestUtils {
} }
} }
public void setOutDirSuffix(String suffix) {
args.setOutDir(new File("test-out-" + suffix + "-tmp"));
}
public String getTestName() { public String getTestName() {
return this.getClass().getSimpleName(); return this.getClass().getSimpleName();
} }
......
...@@ -31,6 +31,10 @@ public enum TestProfile implements Consumer<IntegrationTest> { ...@@ -31,6 +31,10 @@ public enum TestProfile implements Consumer<IntegrationTest> {
test.useTargetJavaVersion(11); test.useTargetJavaVersion(11);
test.useJavaInput(); test.useJavaInput();
}), }),
JAVA17("java-17", test -> {
test.useTargetJavaVersion(17);
test.useJavaInput();
}),
ECJ_DX_J8("ecj-dx-j8", test -> { ECJ_DX_J8("ecj-dx-j8", test -> {
test.useEclipseCompiler(); test.useEclipseCompiler();
test.useTargetJavaVersion(8); test.useTargetJavaVersion(8);
...@@ -52,8 +56,9 @@ public enum TestProfile implements Consumer<IntegrationTest> { ...@@ -52,8 +56,9 @@ public enum TestProfile implements Consumer<IntegrationTest> {
} }
@Override @Override
public void accept(IntegrationTest integrationTest) { public void accept(IntegrationTest test) {
this.setup.accept(integrationTest); this.setup.accept(test);
test.setOutDirSuffix(description);
} }
public String getDescription() { public String getDescription() {
......
package jadx.tests.integration.trycatch;
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 TestTryCatchFinally14 extends IntegrationTest {
@SuppressWarnings("unused")
public static class TestCls {
private TCls t;
public void test() {
try {
if (t != null) {
t.doSomething();
}
} finally {
if (t != null) {
t.doFinally();
}
}
}
private static class TCls {
public void doSomething() {
}
public void doFinally() {
}
}
}
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.D8_J11, TestProfile.JAVA8 })
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne(".doSomething();")
.containsOne("} finally {")
.containsOne(".doFinally();")
.countString(2, "!= null) {");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册