fix: split CONST used in PHI to help type inference (#900)

This commit is contained in:
Skylot
2020-04-26 20:36:27 +01:00
parent 4dc4aa122b
commit a7f315f596
5 changed files with 202 additions and 17 deletions
@@ -10,6 +10,7 @@ import jadx.core.dex.attributes.AFlag;
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.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
@@ -83,6 +84,20 @@ public final class PhiInsn extends InsnNode {
return reg;
}
@Nullable
public RegisterArg getArgBySsaVar(SSAVar ssaVar) {
if (getArgsCount() == 0) {
return null;
}
for (InsnArg insnArg : getArguments()) {
RegisterArg reg = (RegisterArg) insnArg;
if (reg.getSVar() == ssaVar) {
return reg;
}
}
return null;
}
@Override
public boolean replaceArg(InsnArg from, InsnArg to) {
if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) {
@@ -43,6 +43,7 @@ import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
@JadxVisitor(
@@ -74,29 +75,26 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (Consts.DEBUG) {
LOG.info("Start type inference in method: {}", mth);
}
boolean resolved = runTypePropagation(mth);
if (!resolved) {
boolean moveAdded = false;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
if (tryInsertAdditionalInsn(mth, var)) {
moveAdded = true;
}
}
if (moveAdded) {
InitCodeVariables.rerun(mth);
resolved = runTypePropagation(mth);
}
if (!resolved) {
resolved = runMultiVariableSearch(mth);
}
}
if (resolved) {
if (resolveTypes(mth)) {
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
processIncompatiblePrimitives(mth, var);
}
}
}
private boolean resolveTypes(MethodNode mth) {
if (runTypePropagation(mth)) {
return true;
}
if (trySplitConstInsns(mth)) {
return true;
}
if (tryInsertAdditionalMove(mth)) {
return true;
}
return runMultiVariableSearch(mth);
}
/**
* Guess type from usage and try to set it to current variable
* and all connected instructions with {@link TypeUpdate#apply(SSAVar, ArgType)}
@@ -354,6 +352,64 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false;
}
private boolean trySplitConstInsns(MethodNode mth) {
boolean constSplitted = false;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
if (checkAndSplitConstInsn(mth, var)) {
constSplitted = true;
}
}
if (!constSplitted) {
return false;
}
InitCodeVariables.rerun(mth);
return runTypePropagation(mth);
}
private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) {
if (var.getUsedInPhi().size() < 2) {
return false;
}
InsnNode assignInsn = var.getAssign().getAssignInsn();
InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST);
if (constInsn == null) {
return false;
}
BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn);
if (blockNode == null) {
return false;
}
// for every PHI make separate CONST insn
boolean first = true;
for (PhiInsn phiInsn : var.getUsedInPhi()) {
if (first) {
first = false;
continue;
}
InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth);
copyInsn.add(AFlag.SYNTHETIC);
BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn);
RegisterArg phiArg = phiInsn.getArgBySsaVar(var);
phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate());
}
return true;
}
private boolean tryInsertAdditionalMove(MethodNode mth) {
boolean moveAdded = false;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
if (tryInsertAdditionalInsn(mth, var)) {
moveAdded = true;
}
}
if (!moveAdded) {
return false;
}
InitCodeVariables.rerun(mth);
return runTypePropagation(mth);
}
/**
* Add MOVE instruction before PHI in bound blocks to make 'soft' type link.
* This allows to use different types in blocks merged by PHI.
@@ -663,6 +663,19 @@ public class BlockUtils {
return false;
}
public static boolean insertAfterInsn(BlockNode block, InsnNode insn, InsnNode newInsn) {
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
for (int i = 0; i < size; i++) {
InsnNode instruction = instructions.get(i);
if (instruction == insn) {
instructions.add(i + 1, newInsn);
return true;
}
}
return false;
}
public static boolean replaceInsn(MethodNode mth, InsnNode oldInsn, InsnNode newInsn) {
for (BlockNode block : mth.getBasicBlocks()) {
if (replaceInsn(mth, block, oldInsn, newInsn)) {
@@ -0,0 +1,38 @@
package jadx.tests.integration.types;
import org.junit.jupiter.api.Test;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestTypeResolver14 extends SmaliTest {
// @formatter:off
/*
public Date test() throws Exception {
Date date = null;
Long l = null;
Cursor query = DBUtil.query(false, (CancellationSignal) null);
try {
if (query.moveToFirst()) {
if (!query.isNull(0)) {
l = Long.valueOf(query.getLong(0));
}
date = this.this$0.toDate(l);
}
return date;
} finally {
query.close();
}
}
*/
// @formatter:on
@Test
public void test() {
disableCompilation();
assertThat(getClassNodeFromSmali())
.code()
.doesNotContain("? r2");
}
}
@@ -0,0 +1,63 @@
.class public Ltypes/TestTypeResolver14;
.super Ljava/lang/Object;
.source "SourceFile"
.method public test()Ljava/util/Date;
.registers 5
.annotation system Ldalvik/annotation/Throws;
value = {
Ljava/lang/Exception;
}
.end annotation
.line 472
const/4 v2, 0x0
const/4 v3, 0x0
invoke-static {v3, v2}, Landroidx/room/util/DBUtil;->query(ZLandroid/os/CancellationSignal;)Landroid/database/Cursor;
move-result-object v0
.line 475
:try_start_e
invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z
move-result v1
if-eqz v1, :cond_2d
.line 477
invoke-interface {v0, v3}, Landroid/database/Cursor;->isNull(I)Z
move-result v1
if-eqz v1, :cond_1b
goto :goto_23
.line 480
:cond_1b
invoke-interface {v0, v3}, Landroid/database/Cursor;->getLong(I)J
move-result-wide v1
invoke-static {v1, v2}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
move-result-object v2
.line 482
:goto_23
iget-object v1, p0, Ltypes/TestTypeResolver14$8;->this$0:Ltypes/TestTypeResolver14;
invoke-virtual {v1, v2}, Ltypeconverters/DateTypeConverter;->toDate(Ljava/lang/Long;)Ljava/util/Date;
move-result-object v2
:try_end_2d
.catchall {:try_start_e .. :try_end_2d} :catchall_31
.line 488
:cond_2d
invoke-interface {v0}, Landroid/database/Cursor;->close()V
return-object v2
:catchall_31
move-exception v1
invoke-interface {v0}, Landroid/database/Cursor;->close()V
.line 489
throw v1
.end method