diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index 4e38809b7..a678a40fa 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -292,6 +292,17 @@ public abstract class InsnArg extends Typed { return false; } + public boolean isSameVar(SSAVar ssaVar) { + if (ssaVar == null) { + return false; + } + if (isRegister()) { + SSAVar thisSsaVar = ((RegisterArg) this).getSVar(); + return Objects.equals(thisSsaVar, ssaVar); + } + return false; + } + public boolean isSameCodeVar(RegisterArg arg) { if (arg == null) { return false; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java index 5041be98e..551b42468 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java @@ -23,6 +23,7 @@ import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.PhiInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -32,6 +33,7 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; @@ -70,6 +72,7 @@ public final class FixTypesVisitor extends AbstractVisitor { this.typeUpdate = root.getTypeUpdate(); this.typeInference.init(root); this.resolvers = Arrays.asList( + this::applyFieldType, this::tryRestoreTypeVarCasts, this::tryInsertCasts, this::tryDeduceTypes, @@ -292,6 +295,117 @@ public final class FixTypesVisitor extends AbstractVisitor { return false; } + /** + * Use type for var assigned from field (IGET or SGET). + * Insert additional casts at var use places. + */ + private Boolean applyFieldType(MethodNode mth) { + try { + boolean changed = false; + // will add new SSA vars, can't use for-each loop + List sVars = mth.getSVars(); + for (int i = 0, varsCount = sVars.size(); i < varsCount; i++) { + SSAVar ssaVar = sVars.get(i); + if (tryFieldTypeWithNewCasts(mth, ssaVar, true)) { + changed = true; + } + } + if (!changed) { + return false; + } + // rerun full type inference + InitCodeVariables.rerun(mth); + typeInference.initTypeBounds(mth); + typeInference.runTypePropagation(mth); + + // check if changed var types are fixed + boolean success = true; + for (SSAVar ssaVar : mth.getSVars()) { + if (tryFieldTypeWithNewCasts(mth, ssaVar, false)) { + success = false; + } + } + if (!success) { + typeInference.initTypeBounds(mth); + typeInference.runTypePropagation(mth); + mth.addWarnComment("Type inference incomplete: some casts might be missing"); + } + return success; + } catch (Exception e) { + mth.addWarnComment("Type inference fix 'apply assigned field type' failed", e); + return false; + } + } + + private boolean tryFieldTypeWithNewCasts(MethodNode mth, SSAVar ssaVar, boolean insertCasts) { + ArgType type = ssaVar.getTypeInfo().getType(); + if (type.isTypeKnown() || ssaVar.isTypeImmutable()) { + return false; + } + InsnNode assignInsn = ssaVar.getAssignInsn(); + if (assignInsn == null) { + return false; + } + InsnType insnType = assignInsn.getType(); + if (insnType != InsnType.IGET && insnType != InsnType.SGET) { + return false; + } + ArgType fieldType = assignInsn.getResult().getInitType(); + // field type should be used + if (insertCasts) { + // try to find a use place and insert cast + boolean inserted = false; + for (RegisterArg useArg : ssaVar.getUseList()) { + if (insertExplicitUseCast(mth, ssaVar, useArg, fieldType)) { + inserted = true; + } + } + return inserted; + } + // force field type, will make type inference incomplete, + // but it is better that completely unknown type + ssaVar.setType(fieldType); + return true; + } + + private boolean insertExplicitUseCast(MethodNode mth, SSAVar ssaVar, RegisterArg useArg, ArgType fieldType) { + InsnNode parentInsn = useArg.getParentInsn(); + if (!InsnUtils.isInsnType(parentInsn, InsnType.INVOKE)) { + return false; + } + InvokeNode invoke = (InvokeNode) parentInsn; + InsnArg instanceArg = invoke.getInstanceArg(); + if (instanceArg == null || !instanceArg.isSameVar(ssaVar)) { + return false; + } + IMethodDetails details = mth.root().getMethodUtils().getMethodDetails(invoke); + if (details == null) { + return false; + } + int newCasts = 0; + int k = -1; + for (InsnArg invArg : invoke.getArgList()) { + if (invArg == instanceArg) { + continue; + } + k++; + if (!invArg.isRegister()) { + continue; + } + ArgType detailsArg = details.getArgTypes().get(k); + ArgType invArgType = invArg.getType(); + ArgType resolvedType = mth.root().getTypeUtils().replaceClassGenerics(fieldType, invArgType, detailsArg); + if (resolvedType != null && !resolvedType.equals(invArgType)) { + IndexInsnNode castInsn = insertUseCast(mth, (RegisterArg) invArg, resolvedType); + if (castInsn != null) { + castInsn.add(AFlag.EXPLICIT_CAST); + newCasts++; + } + } + } + return newCasts > 0; + } + /** * Fix check casts to type var extend type: *
@@ -372,7 +486,9 @@ public final class FixTypesVisitor extends AbstractVisitor { && !boundType.equals(var.getTypeInfo().getType()) && boundType.containsTypeVariable() && !mth.root().getTypeUtils().containsUnknownTypeVar(mth, boundType)) { - if (insertAssignCast(mth, var, boundType)) { + IndexInsnNode castInsn = insertAssignCast(mth, var, boundType); + if (castInsn != null) { + castInsn.add(AFlag.SOFT_CAST); return 1; } return insertUseCasts(mth, var); @@ -388,58 +504,65 @@ public final class FixTypesVisitor extends AbstractVisitor { } int useCasts = 0; for (RegisterArg useReg : new ArrayList<>(useList)) { - if (insertSoftUseCast(mth, useReg)) { + IndexInsnNode castInsn = insertUseCast(mth, useReg, useReg.getInitType()); + if (castInsn != null) { + castInsn.add(AFlag.SOFT_CAST); useCasts++; } } return useCasts; } - private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) { + private @Nullable IndexInsnNode insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) { RegisterArg assignArg = var.getAssign(); InsnNode assignInsn = assignArg.getParentInsn(); if (assignInsn == null || assignInsn.getType() == InsnType.PHI) { - return false; + return null; } BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn); if (assignBlock == null) { - return false; + return null; } assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth)); - IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType); - return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn); + IndexInsnNode castInsn = makeCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType); + if (!BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn)) { + return null; + } + return castInsn; } - private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) { + private @Nullable IndexInsnNode insertUseCast(MethodNode mth, RegisterArg useArg, ArgType castType) { InsnNode useInsn = useArg.getParentInsn(); if (useInsn == null || useInsn.getType() == InsnType.PHI) { - return false; + return null; } if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroConst()) { // cast isn't needed if compare with null - return false; + return null; } BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn); if (useBlock == null) { - return false; + return null; } - IndexInsnNode castInsn = makeSoftCastInsn( + IndexInsnNode castInsn = makeCastInsn( useArg.duplicateWithNewSSAVar(mth), useArg.duplicate(), - useArg.getInitType()); + castType); useInsn.replaceArg(useArg, castInsn.getResult().duplicate()); - boolean success = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn); - if (Consts.DEBUG_TYPE_INFERENCE && success) { - LOG.info("Insert soft cast for {} before {} in {}", useArg, useInsn, useBlock); + boolean inserted = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn); + if (!inserted) { + return null; } - return success; + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.info("Insert cast for {} before {} in {}", useArg, useInsn, useBlock); + } + return castInsn; } - private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) { + private IndexInsnNode makeCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) { IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1); castInsn.setResult(result); castInsn.addArg(arg); - castInsn.add(AFlag.SOFT_CAST); castInsn.add(AFlag.SYNTHETIC); return castInsn; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver26.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver26.java new file mode 100644 index 000000000..0766f3d85 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver26.java @@ -0,0 +1,31 @@ +package jadx.tests.integration.types; + +import java.util.ArrayList; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestTypeResolver26 extends IntegrationTest { + + @SuppressWarnings({ "rawtypes", "unchecked", "checkstyle:IllegalType" }) + public static class TestCls { + final ArrayList target = new ArrayList<>(); + final ArrayList source = new ArrayList(); + + public void test() { + ((ArrayList) target).add(source.get(0)); // cast removed in bytecode + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("type inference failed") + .containsOne("this.target.add((String) this.source.get(0));"); + } +}