fix: resolve type vars from outer class (#1192)
This commit is contained in:
@@ -31,8 +31,12 @@ public class ClassTypeVarsAttr implements IAttribute {
|
||||
return typeVars;
|
||||
}
|
||||
|
||||
public Map<String, Map<ArgType, ArgType>> getSuperTypeMaps() {
|
||||
return superTypeMaps;
|
||||
public Map<ArgType, ArgType> getTypeVarsMapFor(ArgType type) {
|
||||
Map<ArgType, ArgType> typeMap = superTypeMaps.get(type.getObject());
|
||||
if (typeMap == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return typeMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -805,6 +805,10 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgType outerType = getOuterType();
|
||||
if (outerType != null) {
|
||||
return outerType.containsTypeVariable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (isArray()) {
|
||||
|
||||
@@ -164,19 +164,41 @@ public class TypeUtils {
|
||||
if (typeWithGeneric == null || genericSourceType == null) {
|
||||
return null;
|
||||
}
|
||||
Map<ArgType, ArgType> typeVarsMap;
|
||||
Map<ArgType, ArgType> 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<ArgType, ArgType> mergeTypeMaps(Map<ArgType, ArgType> base, Map<ArgType, ArgType> addition) {
|
||||
if (base.isEmpty()) {
|
||||
return addition;
|
||||
}
|
||||
if (addition.isEmpty()) {
|
||||
return base;
|
||||
}
|
||||
Map<ArgType, ArgType> map = new HashMap<>(base.size() + addition.size());
|
||||
for (Map.Entry<ArgType, ArgType> 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<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
|
||||
if (!clsType.isGeneric()) {
|
||||
return Collections.emptyMap();
|
||||
@@ -274,13 +296,25 @@ public class TypeUtils {
|
||||
return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound());
|
||||
}
|
||||
|
||||
List<ArgType> genericTypes = replaceType.getGenericTypes();
|
||||
if (replaceType.isGeneric() && notEmpty(genericTypes)) {
|
||||
List<ArgType> 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<ArgType> genericTypes = replaceType.getGenericTypes();
|
||||
if (notEmpty(genericTypes)) {
|
||||
List<ArgType> newTypes = Utils.collectionMap(genericTypes, t -> {
|
||||
ArgType type = replaceTypeVariablesUsingMap(t, replaceMap);
|
||||
return type == null ? t : type;
|
||||
});
|
||||
return ArgType.generic(replaceType, newTypes);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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<K, V>$Entry<K, V>"));
|
||||
assertTrue(genericInner.containsTypeVariable());
|
||||
|
||||
ArgType genericInner2 = ArgType.outerGeneric(base, ArgType.object("Entry"));
|
||||
LOG.debug("genericInner2: {}", genericInner2);
|
||||
|
||||
assertThat(genericInner.toString(), is("java.util.Map<K, V>$Entry<K, V>"));
|
||||
assertThat(genericInner2.toString(), is("java.util.Map<K, V>$Entry"));
|
||||
assertTrue(genericInner2.containsTypeVariable());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArgType, ArgType> 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<ArgType, ArgType> typeMap, ArgType expected) {
|
||||
ArgType resultType = typeUtils.replaceTypeVariablesUsingMap(typeVar, typeMap);
|
||||
assertThat(resultType)
|
||||
|
||||
+62
@@ -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<X> {
|
||||
Map.Entry<X, X> entry();
|
||||
}
|
||||
|
||||
public static class Outer<Y> {
|
||||
public class Inner implements I<Y> {
|
||||
@Override
|
||||
public Map.Entry<Y, Y> entry() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Inner getInner() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Outer<String> outer;
|
||||
|
||||
public void test() {
|
||||
Outer<String>.Inner inner = this.outer.getInner();
|
||||
use(inner, inner);
|
||||
Map.Entry<String, String> entry = inner.entry();
|
||||
use(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
public void test2() {
|
||||
// force interface virtual call
|
||||
I<String> base = this.outer.getInner();
|
||||
use(base, base);
|
||||
Map.Entry<String, String> 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<Y>.Inner inner")
|
||||
.doesNotContain("Object entry = ")
|
||||
.countString(2, "Outer<String>.Inner inner = this.outer.getInner();")
|
||||
.countString(2, "Map.Entry<String, String> entry = ");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user