diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/CodeVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/CodeVar.java index 32f77766f..11ad8bf16 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/CodeVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/CodeVar.java @@ -6,7 +6,7 @@ import java.util.List; public class CodeVar { private String name; - private ArgType type; + private ArgType type; // nullable before type inference, set only for immutable types private List ssaVars = new ArrayList<>(3); private boolean isFinal; diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java index 8570792b6..cbac95191 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java @@ -139,6 +139,9 @@ public class SSAVar extends AttrNode { public void setCodeVar(@NotNull CodeVar codeVar) { this.codeVar = codeVar; + if (codeVar.getType() != null && !typeInfo.getType().equals(codeVar.getType())) { + throw new JadxRuntimeException("Unmached types for SSA and Code variables: " + this + " and " + codeVar); + } codeVar.addSsaVar(this); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java index 504c896cc..1cf408de3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java @@ -1,16 +1,20 @@ package jadx.core.dex.visitors; -import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.PhiInsn; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; @JadxVisitor( name = "InitCodeVariables", @@ -41,7 +45,6 @@ public class InitCodeVariables extends AbstractVisitor { return; } CodeVar codeVar = new CodeVar(); - codeVar.setType(ssaVar.getTypeInfo().getType()); RegisterArg assignArg = ssaVar.getAssign(); if (assignArg.contains(AFlag.THIS)) { codeVar.setName(RegisterArg.THIS_ARG_NAME); @@ -55,17 +58,36 @@ public class InitCodeVariables extends AbstractVisitor { } private static void setCodeVar(SSAVar ssaVar, CodeVar codeVar) { - ssaVar.setCodeVar(codeVar); PhiInsn usedInPhi = ssaVar.getUsedInPhi(); if (usedInPhi != null) { - Set vars = new HashSet<>(); + Set vars = new LinkedHashSet<>(); + vars.add(ssaVar); collectConnectedVars(usedInPhi, vars); + setCodeVarType(codeVar, vars); vars.forEach(var -> { if (var.isCodeVarSet()) { codeVar.mergeFlagsFrom(var.getCodeVar()); } var.setCodeVar(codeVar); }); + } else { + ssaVar.setCodeVar(codeVar); + } + } + + private static void setCodeVarType(CodeVar codeVar, Set vars) { + if (vars.size() > 1) { + List imTypes = vars.stream() + .filter(var -> var.contains(AFlag.METHOD_ARGUMENT)) + .map(var -> var.getTypeInfo().getType()) + .distinct() + .collect(Collectors.toList()); + int imCount = imTypes.size(); + if (imCount == 1) { + codeVar.setType(imTypes.get(0)); + } else if (imCount > 1) { + throw new JadxRuntimeException("Several immutable types in one variable: " + imTypes + ", vars: " + vars); + } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 5db6744b3..1143dd37d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -102,51 +102,58 @@ public final class TypeInferenceVisitor extends AbstractVisitor { private boolean setBestType(SSAVar ssaVar) { try { + ArgType codeVarType = ssaVar.getCodeVar().getType(); + if (codeVarType != null) { + return applyImmutableType(ssaVar, codeVarType); + } RegisterArg assignArg = ssaVar.getAssign(); - if (!assignArg.isTypeImmutable()) { - return calculateFromBounds(ssaVar); + if (assignArg.isTypeImmutable()) { + return applyImmutableType(ssaVar, assignArg.getInitType()); } - ArgType initType = assignArg.getInitType(); - TypeUpdateResult result = typeUpdate.apply(ssaVar, initType); - if (result == TypeUpdateResult.REJECT) { - if (Consts.DEBUG) { - LOG.info("Initial immutable type set rejected: {} -> {}", ssaVar, initType); - } - return false; - } - return true; + return calculateFromBounds(ssaVar); } catch (Exception e) { LOG.error("Failed to calculate best type for var: {}", ssaVar); return false; } } + private boolean applyImmutableType(SSAVar ssaVar, ArgType initType) { + TypeUpdateResult result = typeUpdate.apply(ssaVar, initType); + if (result == TypeUpdateResult.REJECT) { + if (Consts.DEBUG) { + LOG.info("Initial immutable type set rejected: {} -> {}", ssaVar, initType); + } + return false; + } + return result == TypeUpdateResult.CHANGED; + } + private boolean calculateFromBounds(SSAVar ssaVar) { TypeInfo typeInfo = ssaVar.getTypeInfo(); Set bounds = typeInfo.getBounds(); Optional bestTypeOpt = selectBestTypeFromBounds(bounds); - if (bestTypeOpt.isPresent()) { - ArgType candidateType = bestTypeOpt.get(); - TypeUpdateResult result = typeUpdate.apply(ssaVar, candidateType); - if (result == TypeUpdateResult.REJECT) { - if (Consts.DEBUG) { - if (ssaVar.getTypeInfo().getType().equals(candidateType)) { - LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); - } else if (candidateType.isTypeKnown()) { - LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); - } + if (!bestTypeOpt.isPresent()) { + if (Consts.DEBUG) { + LOG.warn("Failed to select best type from bounds, count={} : ", bounds.size()); + for (ITypeBound bound : bounds) { + LOG.warn(" {}", bound); } - return false; } - return result == TypeUpdateResult.CHANGED; + return false; } - if (Consts.DEBUG) { - LOG.warn("Failed to select best type from bounds, count={} : ", bounds.size()); - for (ITypeBound bound : bounds) { - LOG.warn(" {}", bound); + ArgType candidateType = bestTypeOpt.get(); + TypeUpdateResult result = typeUpdate.apply(ssaVar, candidateType); + if (result == TypeUpdateResult.REJECT) { + if (Consts.DEBUG) { + if (ssaVar.getTypeInfo().getType().equals(candidateType)) { + LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); + } else if (candidateType.isTypeKnown()) { + LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); + } } + return false; } - return false; + return result == TypeUpdateResult.CHANGED; } private Optional selectBestTypeFromBounds(Set bounds) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java index 60caa355f..ec108c91a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java @@ -1,6 +1,6 @@ package jadx.core.dex.visitors.typeinference; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.jetbrains.annotations.NotNull; @@ -10,7 +10,7 @@ import jadx.core.dex.instructions.args.ArgType; public class TypeInfo { private ArgType type = ArgType.UNKNOWN; - private final Set bounds = new HashSet<>(); + private final Set bounds = new LinkedHashSet<>(); @NotNull public ArgType getType() { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 391249f99..9362dee52 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -48,7 +48,7 @@ public final class TypeUpdate { if (candidateType == null) { return REJECT; } - if (!candidateType.isTypeKnown() && ssaVar.getTypeInfo().getType().isTypeKnown()) { + if (!candidateType.isTypeKnown()/* && ssaVar.getTypeInfo().getType().isTypeKnown()*/) { return REJECT; } @@ -86,14 +86,14 @@ public final class TypeUpdate { return SAME; } TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType); - if (compareResult == TypeCompareEnum.CONFLICT) { - if (Consts.DEBUG) { - LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType); - } - return REJECT; - } if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) { - // don't changed type, conflict already rejected + // don't changed type + if (compareResult == TypeCompareEnum.CONFLICT) { + if (Consts.DEBUG) { + LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType); + } + return REJECT; + } return SAME; } if (compareResult.isWider()) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally6.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally6.java index 447b566bc..7f5a98df8 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally6.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally6.java @@ -56,13 +56,13 @@ public class TestTryCatchFinally6 extends IntegrationTest { String code = cls.getCode().toString(); assertThat(code, containsLines(2, - "FileInputStream fileInputStream = null;", + "InputStream inputStream = null;", "try {", indent() + "call();", - indent() + "fileInputStream = new FileInputStream(\"1.txt\");", + indent() + "inputStream = new FileInputStream(\"1.txt\");", "} finally {", - indent() + "if (fileInputStream != null) {", - indent() + indent() + "fileInputStream.close();", + indent() + "if (inputStream != null) {", + indent() + indent() + "inputStream.close();", indent() + "}", "}" )); diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java index 9837a4392..502cf8246 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java @@ -24,7 +24,7 @@ public class TestVariablesGeneric extends SmaliTest { @Test public void test() { disableCompilation(); - ClassNode cls = getClassNodeFromSmaliWithPath("variables", "TestVariablesGeneric"); + ClassNode cls = getClassNodeFromSmaliWithPkg("variables", "TestVariablesGeneric"); String code = cls.getCode().toString(); assertThat(code, not(containsString("iVar2"))); diff --git a/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali b/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali index 29ae8ee07..dd9c78ea8 100644 --- a/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali +++ b/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali @@ -1,4 +1,4 @@ -.class public LTestVariablesGeneric; +.class public Lvariables/TestVariablesGeneric; .super Ljava/lang/Object; .source "SourceFile"