From fef3e21c70801c6d4d0ffb6df9a8943767a123f7 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 19 Jun 2021 13:44:15 +0100 Subject: [PATCH] fix: resolve type vars from outer class (#1192) --- .../attributes/nodes/ClassTypeVarsAttr.java | 8 ++- .../core/dex/instructions/args/ArgType.java | 4 ++ .../jadx/core/dex/nodes/utils/TypeUtils.java | 60 ++++++++++++++---- .../dex/instructions/args/ArgTypeTest.java | 8 +-- .../core/dex/nodes/utils/TypeUtilsTest.java | 23 +++++++ .../generics/TestTypeVarsFromOuterClass.java | 62 +++++++++++++++++++ 6 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromOuterClass.java diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java index 7cfe20275..f6cbe8084 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java @@ -31,8 +31,12 @@ public class ClassTypeVarsAttr implements IAttribute { return typeVars; } - public Map> getSuperTypeMaps() { - return superTypeMaps; + public Map getTypeVarsMapFor(ArgType type) { + Map typeMap = superTypeMaps.get(type.getObject()); + if (typeMap == null) { + return Collections.emptyMap(); + } + return typeMap; } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 25d4d829b..67136ef26 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -805,6 +805,10 @@ public abstract class ArgType { } } } + ArgType outerType = getOuterType(); + if (outerType != null) { + return outerType.containsTypeVariable(); + } return false; } if (isArray()) { 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 f8c2e8a54..0a6a977ca 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 @@ -164,19 +164,41 @@ public class TypeUtils { if (typeWithGeneric == null || genericSourceType == null) { return null; } - Map typeVarsMap; + Map typeVarsMap = Collections.emptyMap(); ClassTypeVarsAttr typeVars = getClassTypeVars(instanceType); if (typeVars != null) { - typeVarsMap = typeVars.getSuperTypeMaps().get(genericSourceType.getObject()); - } else { - typeVarsMap = getTypeVariablesMapping(instanceType); + typeVarsMap = mergeTypeMaps(typeVarsMap, typeVars.getTypeVarsMapFor(genericSourceType)); } - if (typeVarsMap == null) { - return null; + typeVarsMap = mergeTypeMaps(typeVarsMap, getTypeVariablesMapping(instanceType)); + ArgType outerType = instanceType.getOuterType(); + while (outerType != null) { + typeVarsMap = mergeTypeMaps(typeVarsMap, getTypeVariablesMapping(outerType)); + outerType = outerType.getOuterType(); } return replaceTypeVariablesUsingMap(typeWithGeneric, typeVarsMap); } + private static Map mergeTypeMaps(Map base, Map addition) { + if (base.isEmpty()) { + return addition; + } + if (addition.isEmpty()) { + return base; + } + Map map = new HashMap<>(base.size() + addition.size()); + for (Map.Entry entry : base.entrySet()) { + ArgType value = entry.getValue(); + ArgType type = addition.remove(value); + if (type != null) { + map.put(entry.getKey(), type); + } else { + map.put(entry.getKey(), entry.getValue()); + } + } + map.putAll(addition); + return map; + } + public Map getTypeVariablesMapping(ArgType clsType) { if (!clsType.isGeneric()) { return Collections.emptyMap(); @@ -274,13 +296,25 @@ public class TypeUtils { return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound()); } - List genericTypes = replaceType.getGenericTypes(); - if (replaceType.isGeneric() && notEmpty(genericTypes)) { - List newTypes = Utils.collectionMap(genericTypes, t -> { - ArgType type = replaceTypeVariablesUsingMap(t, replaceMap); - return type == null ? t : type; - }); - return ArgType.generic(replaceType, newTypes); + if (replaceType.isGeneric()) { + ArgType outerType = replaceType.getOuterType(); + if (outerType != null) { + ArgType replacedOuter = replaceTypeVariablesUsingMap(outerType, replaceMap); + if (replacedOuter == null) { + return null; + } + ArgType innerType = replaceType.getInnerType(); + ArgType replacedInner = replaceTypeVariablesUsingMap(innerType, replaceMap); + return ArgType.outerGeneric(replacedOuter, replacedInner == null ? innerType : replacedInner); + } + List genericTypes = replaceType.getGenericTypes(); + if (notEmpty(genericTypes)) { + List newTypes = Utils.collectionMap(genericTypes, t -> { + ArgType type = replaceTypeVariablesUsingMap(t, replaceMap); + return type == null ? t : type; + }); + return ArgType.generic(replaceType, newTypes); + } } return null; } diff --git a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java index 8a70a8e2d..2a99d682b 100644 --- a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java @@ -36,11 +36,11 @@ class ArgTypeTest { ArgType base = ArgType.generic("java.util.Map", genericTypes); ArgType genericInner = ArgType.outerGeneric(base, ArgType.generic("Entry", genericTypes)); - LOG.debug("genericInner : {}", genericInner); + assertThat(genericInner.toString(), is("java.util.Map$Entry")); + assertTrue(genericInner.containsTypeVariable()); ArgType genericInner2 = ArgType.outerGeneric(base, ArgType.object("Entry")); - LOG.debug("genericInner2: {}", genericInner2); - - assertThat(genericInner.toString(), is("java.util.Map$Entry")); + assertThat(genericInner2.toString(), is("java.util.Map$Entry")); + assertTrue(genericInner2.containsTypeVariable()); } } diff --git a/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java b/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java index 003975c48..b349228ae 100644 --- a/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java @@ -1,6 +1,7 @@ package jadx.core.dex.nodes.utils; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -10,11 +11,13 @@ import jadx.api.JadxArgs; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.RootNode; +import static jadx.core.dex.instructions.args.ArgType.EXCEPTION; import static jadx.core.dex.instructions.args.ArgType.STRING; import static jadx.core.dex.instructions.args.ArgType.array; import static jadx.core.dex.instructions.args.ArgType.generic; import static jadx.core.dex.instructions.args.ArgType.genericType; import static jadx.core.dex.instructions.args.ArgType.object; +import static jadx.core.dex.instructions.args.ArgType.outerGeneric; import static org.assertj.core.api.Assertions.assertThat; class TypeUtilsTest { @@ -36,6 +39,26 @@ class TypeUtilsTest { replaceTypeVar(array(typeVar), typeMap, array(STRING)); } + @Test + void replaceTypeVariablesUsingMap2() { + ArgType kVar = genericType("K"); + ArgType vVar = genericType("V"); + ArgType mapCls = object("java.util.Map"); + ArgType entryCls = object("Entry"); + ArgType typedMap = generic(mapCls, kVar, vVar); + ArgType typedEntry = generic(entryCls, kVar, vVar); + + Map typeMap = new HashMap<>(); + typeMap.put(kVar, STRING); + typeMap.put(vVar, EXCEPTION); + + ArgType replacedMap = typeUtils.replaceTypeVariablesUsingMap(typedMap, typeMap); + ArgType replacedEntry = typeUtils.replaceTypeVariablesUsingMap(typedEntry, typeMap); + + replaceTypeVar(outerGeneric(typedMap, entryCls), typeMap, outerGeneric(replacedMap, entryCls)); + replaceTypeVar(outerGeneric(typedMap, typedEntry), typeMap, outerGeneric(replacedMap, replacedEntry)); + } + private void replaceTypeVar(ArgType typeVar, Map typeMap, ArgType expected) { ArgType resultType = typeUtils.replaceTypeVariablesUsingMap(typeVar, typeMap); assertThat(resultType) diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromOuterClass.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromOuterClass.java new file mode 100644 index 000000000..01c18a3ad --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromOuterClass.java @@ -0,0 +1,62 @@ +package jadx.tests.integration.generics; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestTypeVarsFromOuterClass extends IntegrationTest { + + public static class TestCls { + public interface I { + Map.Entry entry(); + } + + public static class Outer { + public class Inner implements I { + @Override + public Map.Entry entry() { + return null; + } + } + + public Inner getInner() { + return null; + } + } + + private Outer outer; + + public void test() { + Outer.Inner inner = this.outer.getInner(); + use(inner, inner); + Map.Entry entry = inner.entry(); + use(entry.getKey(), entry.getValue()); + } + + public void test2() { + // force interface virtual call + I base = this.outer.getInner(); + use(base, base); + Map.Entry entry = base.entry(); + use(entry.getKey(), entry.getValue()); + } + + public void use(Object a, Object b) { + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("Outer.Inner inner") + .doesNotContain("Object entry = ") + .countString(2, "Outer.Inner inner = this.outer.getInner();") + .countString(2, "Map.Entry entry = "); + } +}