From 7396c7595f4b26d5917c350db1731d958fc1cce8 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 22 Nov 2020 18:40:54 +0000 Subject: [PATCH] fix: resolve type variables in invoke from arg types --- .../jadx/core/dex/nodes/utils/TypeUtils.java | 24 +++++++++ .../dex/visitors/MethodInvokeVisitor.java | 10 +++- .../src/main/java/jadx/core/utils/Utils.java | 20 ++++++++ .../tests/integration/enums/TestEnums9.java | 50 +++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums9.java diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index ebd15acd..3cf60bba 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -154,6 +154,30 @@ public class TypeUtils { return replaceMap; } + public Map getTypeVarMappingForInvoke(BaseInvokeNode invokeInsn) { + IMethodDetails mthDetails = root.getMethodUtils().getMethodDetails(invokeInsn); + if (mthDetails == null) { + return Collections.emptyMap(); + } + Map map = new HashMap<>(1 + invokeInsn.getArgsCount()); + addTypeVarMapping(map, mthDetails.getReturnType(), invokeInsn.getResult()); + int argCount = Math.min(mthDetails.getArgTypes().size(), invokeInsn.getArgsCount()); + for (int i = 0; i < argCount; i++) { + addTypeVarMapping(map, mthDetails.getArgTypes().get(i), invokeInsn.getArg(i)); + } + return map; + } + + private static void addTypeVarMapping(Map map, ArgType typeVar, InsnArg arg) { + if (arg == null || typeVar == null || !typeVar.isTypeKnown()) { + return; + } + if (typeVar.isGenericType()) { + map.put(typeVar, arg.getType()); + } + // TODO: resolve inner type vars: 'List -> List' to 'T -> String' + } + @Nullable public ArgType replaceMethodGenerics(BaseInvokeNode invokeInsn, IMethodDetails details, ArgType typeWithGeneric) { if (typeWithGeneric == null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java index 47f1b80d..e4428469 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java @@ -22,6 +22,7 @@ import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.dex.visitors.methods.MutableMethodDetails; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.typeinference.TypeCompare; @@ -156,9 +157,14 @@ public class MethodInvokeVisitor extends AbstractVisitor { } private Map getTypeVarsMapping(BaseInvokeNode invokeInsn) { - ArgType declClsType = invokeInsn.getCallMth().getDeclClass().getType(); + MethodInfo callMthInfo = invokeInsn.getCallMth(); + ArgType declClsType = callMthInfo.getDeclClass().getType(); ArgType callClsType = getClsCallType(invokeInsn, declClsType); - return root.getTypeUtils().getTypeVariablesMapping(callClsType); + + TypeUtils typeUtils = root.getTypeUtils(); + Map clsTypeVars = typeUtils.getTypeVariablesMapping(callClsType); + Map mthTypeVars = typeUtils.getTypeVarMappingForInvoke(invokeInsn); + return Utils.mergeMaps(clsTypeVars, mthTypeVars); } private ArgType getClsCallType(BaseInvokeNode invokeInsn, ArgType declClsType) { diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 7ff1ed32..45cb361e 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -246,6 +246,22 @@ public class Utils { return Collections.unmodifiableMap(result); } + /** + * Merge two maps. Return HashMap as result. Second map will override values from first map. + */ + public static Map mergeMaps(Map first, Map second) { + if (isEmpty(first)) { + return second; + } + if (isEmpty(second)) { + return first; + } + Map result = new HashMap<>(first.size() + second.size()); + result.putAll(first); + result.putAll(second); + return result; + } + @Nullable public static T getOne(@Nullable List list) { if (list == null || list.size() != 1) { @@ -277,6 +293,10 @@ public class Utils { return col != null && !col.isEmpty(); } + public static boolean isEmpty(Map map) { + return map == null || map.isEmpty(); + } + public static boolean isEmpty(T[] arr) { return arr == null || arr.length == 0; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums9.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums9.java new file mode 100644 index 00000000..0fa0c999 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums9.java @@ -0,0 +1,50 @@ +package jadx.tests.integration.enums; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestEnums9 extends IntegrationTest { + + public static class TestCls { + public enum Types { + INT, + FLOAT, + LONG, + DOUBLE, + OBJECT, + ARRAY; + + private static Set primitives = EnumSet.of(INT, FLOAT, LONG, DOUBLE); + public static List references = new ArrayList<>(); + + static { + references.add(OBJECT); + references.add(ARRAY); + } + + public static Set getPrimitives() { + return primitives; + } + } + + public void check() { + assertThat(Types.getPrimitives()).contains(Types.INT); + assertThat(Types.references).hasSize(2); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("EnumSet.of((Enum) INT,"); + } +} -- GitLab