diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index ef52278ba..993ca5aae 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -389,11 +389,11 @@ public class InsnDecoder { return arrLenInsn; case AGET: - return arrayGet(insn, ArgType.INT_FLOAT); + return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL); case AGET_BOOLEAN: return arrayGet(insn, ArgType.BOOLEAN); case AGET_BYTE: - return arrayGet(insn, ArgType.BYTE); + return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL); case AGET_BYTE_BOOLEAN: return arrayGet(insn, ArgType.BYTE_BOOLEAN); case AGET_CHAR: @@ -406,7 +406,7 @@ public class InsnDecoder { return arrayGet(insn, ArgType.UNKNOWN_OBJECT); case APUT: - return arrayPut(insn, ArgType.INT_FLOAT); + return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL); case APUT_BOOLEAN: return arrayPut(insn, ArgType.BOOLEAN); case APUT_BYTE: @@ -607,16 +607,24 @@ public class InsnDecoder { } private InsnNode arrayGet(InsnData insn, ArgType argType) { + return arrayGet(insn, argType, argType); + } + + private InsnNode arrayGet(InsnData insn, ArgType arrElemType, ArgType resType) { InsnNode inode = new InsnNode(InsnType.AGET, 2); - inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); - inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); + inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType)); + inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType))); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); return inode; } private InsnNode arrayPut(InsnData insn, ArgType argType) { + return arrayPut(insn, argType, argType); + } + + private InsnNode arrayPut(InsnData insn, ArgType arrElemType, ArgType argType) { InsnNode inode = new InsnNode(InsnType.APUT, 3); - inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); + inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType))); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); return inode; 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 a1791017d..0de65a3fb 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 @@ -87,6 +87,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { this::tryDeduceTypes, this::trySplitConstInsns, this::tryToFixIncompatiblePrimitives, + this::tryToForceImmutableTypes, this::tryInsertAdditionalMove, this::runMultiVariableSearch, this::tryRemoveGenerics); @@ -835,6 +836,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (typeInfo.getType().isTypeKnown()) { return false; } + boolean assigned = false; for (ITypeBound bound : typeInfo.getBounds()) { ArgType boundType = bound.getType(); switch (bound.getBound()) { @@ -842,6 +844,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (!boundType.contains(PrimitiveType.BOOLEAN)) { return false; } + assigned = true; break; case USE: if (!boundType.canBeAnyNumber()) { @@ -850,6 +853,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor { break; } } + if (!assigned) { + return false; + } boolean fixed = false; for (ITypeBound bound : typeInfo.getBounds()) { @@ -932,6 +938,36 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return convertInsn; } + private boolean tryToForceImmutableTypes(MethodNode mth) { + boolean fixed = false; + for (SSAVar ssaVar : mth.getSVars()) { + ArgType type = ssaVar.getTypeInfo().getType(); + if (!type.isTypeKnown() && ssaVar.isTypeImmutable()) { + if (forceImmutableType(ssaVar)) { + fixed = true; + } + } + } + if (!fixed) { + return false; + } + return runTypePropagation(mth); + } + + private boolean forceImmutableType(SSAVar ssaVar) { + for (RegisterArg useArg : ssaVar.getUseList()) { + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn != null) { + InsnType insnType = parentInsn.getType(); + if (insnType == InsnType.AGET || insnType == InsnType.APUT) { + ssaVar.setType(ssaVar.getImmutableType()); + return true; + } + } + } + return false; + } + private static void assignImmutableTypes(MethodNode mth) { for (SSAVar ssaVar : mth.getSVars()) { ArgType immutableType = getSsaImmutableType(ssaVar); 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 1d12df592..6216d67fd 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 @@ -513,7 +513,18 @@ public final class TypeUpdate { private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { if (isAssign(insn, arg)) { - return updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType)); + TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType)); + if (result == REJECT) { + ArgType arrType = insn.getArg(0).getType(); + if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) { + TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement()); + if (compResult == TypeCompareEnum.WIDER) { + // allow implicit upcast for primitive types (int a = byteArr[n]) + return CHANGED; + } + } + } + return result; } InsnArg arrArg = insn.getArg(0); if (arrArg == arg) { @@ -521,7 +532,18 @@ public final class TypeUpdate { if (arrayElement == null) { return REJECT; } - return updateTypeChecked(updateInfo, insn.getResult(), arrayElement); + TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getResult(), arrayElement); + if (result == REJECT) { + ArgType resType = insn.getResult().getType(); + if (resType.isTypeKnown() && resType.isPrimitive()) { + TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement); + if (compResult == TypeCompareEnum.WIDER) { + // allow implicit upcast for primitive types (int a = byteArr[n]) + return CHANGED; + } + } + } + return result; } // index argument return SAME; @@ -538,10 +560,10 @@ public final class TypeUpdate { TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement); if (result == REJECT) { ArgType putType = putArg.getType(); - if (putType.isTypeKnown() && !putType.isPrimitive()) { + if (putType.isTypeKnown()) { TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType); if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) { - // allow wider result (i.e allow put in Object[] any objects) + // allow wider result (i.e. allow put any objects in Object[] or byte in int[]) return CHANGED; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver19.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver19.java new file mode 100644 index 000000000..1bdbd04eb --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver19.java @@ -0,0 +1,44 @@ +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; + +/** + * Issue 1407 + */ +public class TestTypeResolver19 extends SmaliTest { + + public static class TestCls { + public static int[] test(byte[] bArr) { + int[] iArr = new int[bArr.length]; + for (int i = 0; i < bArr.length; i++) { + iArr[i] = bArr[i]; + } + return iArr; + } + + public static int[] test2(byte[] bArr) { + int[] iArr = new int[bArr.length]; + for (int i = 0; i < bArr.length; i++) { + int i2 = bArr[i]; + if (i2 < 0) { + i2 = (int) ((long) i2 & 0xFFFF_FFFFL); + } + iArr[i] = i2; + } + return iArr; + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("iArr[i] = bArr[i];") + .containsOne("iArr[i] = i2;"); + } +}