提交 0541748e 编写于 作者: S Skylot

fix: resolve type variables from super types (#870)

上级 cf1d9e83
...@@ -177,7 +177,7 @@ public class ClspGraph { ...@@ -177,7 +177,7 @@ public class ClspGraph {
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) { private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
if (result.isEmpty()) { if (result.isEmpty()) {
Set<String> empty = Collections.emptySet(); Set<String> empty = Collections.emptySet();
superTypesCache.put(clsName, result); superTypesCache.put(clsName, empty);
return empty; return empty;
} }
superTypesCache.put(clsName, result); superTypesCache.put(clsName, result);
......
...@@ -99,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> { ...@@ -99,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
return this.methodInfo.compareTo(other.methodInfo); return this.methodInfo.compareTo(other.methodInfo);
} }
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (c)";
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
......
...@@ -55,6 +55,11 @@ public class SimpleMethodDetails implements IMethodDetails { ...@@ -55,6 +55,11 @@ public class SimpleMethodDetails implements IMethodDetails {
return AccessFlags.PUBLIC; return AccessFlags.PUBLIC;
} }
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (s)";
}
@Override @Override
public String toString() { public String toString() {
return "SimpleMethodDetails{" + methodInfo + '}'; return "SimpleMethodDetails{" + methodInfo + '}';
......
...@@ -7,6 +7,7 @@ import java.util.Set; ...@@ -7,6 +7,7 @@ import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.fldinit.FieldInitAttr; import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
...@@ -58,6 +59,7 @@ public class AType<T extends IAttribute> { ...@@ -58,6 +59,7 @@ public class AType<T extends IAttribute> {
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>(); public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>(); public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
// field // field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>(); public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
......
package jadx.core.dex.attributes.nodes;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class ClassTypeVarsAttr implements IAttribute {
public static final ClassTypeVarsAttr EMPTY = new ClassTypeVarsAttr(Collections.emptyList(), Collections.emptyMap());
/**
* Type vars defined in current class
*/
private final List<ArgType> typeVars;
/**
* Type vars mapping in current and super types:
* TypeRawObj -> (TypeVarInSuperType -> TypeVarFromThisClass)
*/
private final Map<String, Map<ArgType, ArgType>> superTypeMaps;
public ClassTypeVarsAttr(List<ArgType> typeVars, Map<String, Map<ArgType, ArgType>> superTypeMaps) {
this.typeVars = typeVars;
this.superTypeMaps = superTypeMaps;
}
public List<ArgType> getTypeVars() {
return typeVars;
}
public Map<String, Map<ArgType, ArgType>> getSuperTypeMaps() {
return superTypeMaps;
}
@Override
public AType<ClassTypeVarsAttr> getType() {
return AType.CLASS_TYPE_VARS;
}
@Override
public String toString() {
return "ClassTypeVarsAttr{" + typeVars + ", super maps: " + superTypeMaps + '}';
}
}
...@@ -7,6 +7,7 @@ import java.util.Objects; ...@@ -7,6 +7,7 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly; import org.jetbrains.annotations.TestOnly;
import jadx.core.Consts; import jadx.core.Consts;
...@@ -823,6 +824,7 @@ public abstract class ArgType { ...@@ -823,6 +824,7 @@ public abstract class ArgType {
* Recursively visit all subtypes of this type. * Recursively visit all subtypes of this type.
* To exit return non-null value. * To exit return non-null value.
*/ */
@Nullable
public <R> R visitTypes(Function<ArgType, R> visitor) { public <R> R visitTypes(Function<ArgType, R> visitor) {
R r = visitor.apply(this); R r = visitor.apply(this);
if (r != null) { if (r != null) {
......
...@@ -7,6 +7,7 @@ import java.util.LinkedHashSet; ...@@ -7,6 +7,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -35,6 +36,7 @@ import jadx.core.dex.info.FieldInfo; ...@@ -35,6 +36,7 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
...@@ -68,11 +70,17 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN ...@@ -68,11 +70,17 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
private volatile ProcessState state = ProcessState.NOT_LOADED; private volatile ProcessState state = ProcessState.NOT_LOADED;
private LoadStage loadStage = LoadStage.NONE; private LoadStage loadStage = LoadStage.NONE;
/** Top level classes used in this class (only for top level classes, empty for inners) */ /**
* Top level classes used in this class (only for top level classes, empty for inners)
*/
private List<ClassNode> dependencies = Collections.emptyList(); private List<ClassNode> dependencies = Collections.emptyList();
/** Classes which uses this class */ /**
* Classes which uses this class
*/
private List<ClassNode> useIn = Collections.emptyList(); private List<ClassNode> useIn = Collections.emptyList();
/** Methods which uses this class (by instructions only, definition is excluded) */ /**
* Methods which uses this class (by instructions only, definition is excluded)
*/
private List<MethodNode> useInMth = Collections.emptyList(); private List<MethodNode> useInMth = Collections.emptyList();
// cache maps // cache maps
...@@ -429,6 +437,19 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN ...@@ -429,6 +437,19 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
} }
public void visitSuperTypes(BiConsumer<ArgType, ArgType> consumer) {
TypeUtils typeUtils = root.getTypeUtils();
ArgType thisType = this.getType();
if (!superClass.equals(ArgType.OBJECT)) {
consumer.accept(thisType, superClass);
typeUtils.visitSuperTypes(superClass, consumer);
}
for (ArgType iface : interfaces) {
consumer.accept(thisType, iface);
typeUtils.visitSuperTypes(iface, consumer);
}
}
public boolean hasNotGeneratedParent() { public boolean hasNotGeneratedParent() {
if (contains(AFlag.DONT_GENERATE)) { if (contains(AFlag.DONT_GENERATE)) {
return true; return true;
......
package jadx.core.dex.nodes; package jadx.core.dex.nodes;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
...@@ -24,7 +27,13 @@ import jadx.core.dex.info.AccessInfo.AFType; ...@@ -24,7 +27,13 @@ import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InsnDecoder; import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.args.*; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.args.VisibleVar;
import jadx.core.dex.nodes.VariableNode.VarKind; import jadx.core.dex.nodes.VariableNode.VarKind;
import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
...@@ -680,6 +689,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, ...@@ -680,6 +689,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.compareTo(o.mthInfo); return mthInfo.compareTo(o.mthInfo);
} }
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (m)";
}
@Override @Override
public String toString() { public String toString() {
return parentClass + "." + mthInfo.getName() return parentClass + "." + mthInfo.getName()
......
...@@ -7,11 +7,13 @@ import java.util.HashSet; ...@@ -7,11 +7,13 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.clsp.ClspClass; import jadx.core.clsp.ClspClass;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.BaseInvokeNode;
...@@ -46,6 +48,19 @@ public class TypeUtils { ...@@ -46,6 +48,19 @@ public class TypeUtils {
return generics == null ? Collections.emptyList() : generics; return generics == null ? Collections.emptyList() : generics;
} }
@Nullable
public ClassTypeVarsAttr getClassTypeVars(ArgType type) {
ClassNode classNode = root.resolveClass(type);
if (classNode == null) {
return null;
}
ClassTypeVarsAttr typeVarsAttr = classNode.get(AType.CLASS_TYPE_VARS);
if (typeVarsAttr != null) {
return typeVarsAttr;
}
return buildClassTypeVarsAttr(classNode);
}
public ArgType expandTypeVariables(ClassNode cls, ArgType type) { public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) { if (type.containsTypeVariable()) {
expandTypeVar(cls, type, cls.getGenericTypeParameters()); expandTypeVar(cls, type, cls.getGenericTypeParameters());
...@@ -109,6 +124,26 @@ public class TypeUtils { ...@@ -109,6 +124,26 @@ public class TypeUtils {
return typeVars.isEmpty() ? Collections.emptySet() : typeVars; return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
} }
/**
* Search for unknown type vars at current method. Return only first.
*
* @return unknown type var, null if not found
*/
@Nullable
public ArgType checkForUnknownTypeVars(MethodNode mth, ArgType checkType) {
Set<ArgType> knownTypeVars = getKnownTypeVarsAtMethod(mth);
return checkType.visitTypes(type -> {
if (type.isGenericType() && !knownTypeVars.contains(type)) {
return type;
}
return null;
});
}
public boolean containsUnknownTypeVar(MethodNode mth, ArgType type) {
return checkForUnknownTypeVars(mth, type) != null;
}
/** /**
* Replace generic types in {@code typeWithGeneric} using instance types * Replace generic types in {@code typeWithGeneric} using instance types
* <br> * <br>
...@@ -121,21 +156,31 @@ public class TypeUtils { ...@@ -121,21 +156,31 @@ public class TypeUtils {
*/ */
@Nullable @Nullable
public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) { public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) {
if (typeWithGeneric == null) { return replaceClassGenerics(instanceType, instanceType, typeWithGeneric);
}
@Nullable
public ArgType replaceClassGenerics(ArgType instanceType, ArgType genericSourceType, ArgType typeWithGeneric) {
if (typeWithGeneric == null || genericSourceType == null) {
return null; return null;
} }
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType); Map<ArgType, ArgType> typeVarsMap;
if (replaceMap.isEmpty()) { ClassTypeVarsAttr typeVars = getClassTypeVars(instanceType);
if (typeVars != null) {
typeVarsMap = typeVars.getSuperTypeMaps().get(genericSourceType.getObject());
} else {
typeVarsMap = getTypeVariablesMapping(instanceType);
}
if (typeVarsMap == null) {
return null; return null;
} }
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap); return replaceTypeVariablesUsingMap(typeWithGeneric, typeVarsMap);
} }
public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) { public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
if (!clsType.isGeneric()) { if (!clsType.isGeneric()) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
List<ArgType> typeParameters = root.getTypeUtils().getClassGenerics(clsType); List<ArgType> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
if (typeParameters.isEmpty()) { if (typeParameters.isEmpty()) {
return Collections.emptyMap(); return Collections.emptyMap();
...@@ -239,4 +284,50 @@ public class TypeUtils { ...@@ -239,4 +284,50 @@ public class TypeUtils {
} }
return null; return null;
} }
private ClassTypeVarsAttr buildClassTypeVarsAttr(ClassNode cls) {
Map<String, Map<ArgType, ArgType>> map = new HashMap<>();
ArgType currentClsType = cls.getClassInfo().getType();
map.put(currentClsType.getObject(), getTypeVariablesMapping(currentClsType));
cls.visitSuperTypes((parent, type) -> {
List<ArgType> currentVars = type.getGenericTypes();
if (Utils.isEmpty(currentVars)) {
return;
}
int varsCount = currentVars.size();
List<ArgType> sourceTypeVars = getClassGenerics(type);
if (varsCount == sourceTypeVars.size()) {
Map<ArgType, ArgType> parentTypeMap = map.get(parent.getObject());
Map<ArgType, ArgType> varsMap = new HashMap<>(varsCount);
for (int i = 0; i < varsCount; i++) {
ArgType currentTypeVar = currentVars.get(i);
ArgType resultType = parentTypeMap != null ? parentTypeMap.get(currentTypeVar) : null;
varsMap.put(sourceTypeVars.get(i), resultType != null ? resultType : currentTypeVar);
}
map.put(type.getObject(), varsMap);
}
});
List<ArgType> currentTypeVars = cls.getGenericTypeParameters();
ClassTypeVarsAttr typeVarsAttr = new ClassTypeVarsAttr(currentTypeVars, map);
cls.addAttr(typeVarsAttr);
return typeVarsAttr;
}
public void visitSuperTypes(ArgType type, BiConsumer<ArgType, ArgType> consumer) {
ClassNode cls = root.resolveClass(type);
if (cls != null) {
cls.visitSuperTypes(consumer);
} else {
ClspClass clspClass = root.getClsp().getClsDetails(type);
if (clspClass != null) {
for (ArgType superType : clspClass.getParents()) {
if (!superType.equals(ArgType.OBJECT)) {
consumer.accept(type, superType);
visitSuperTypes(superType, consumer);
}
}
}
}
}
} }
...@@ -86,6 +86,11 @@ public class MutableMethodDetails implements IMethodDetails { ...@@ -86,6 +86,11 @@ public class MutableMethodDetails implements IMethodDetails {
this.accFlags = accFlags; this.accFlags = accFlags;
} }
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (mut)";
}
@Override @Override
public String toString() { public String toString() {
return "Mutable" + toAttrString(); return "Mutable" + toAttrString();
......
...@@ -2,7 +2,9 @@ package jadx.core.dex.visitors.typeinference; ...@@ -2,7 +2,9 @@ package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
/** /**
...@@ -28,22 +30,34 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic { ...@@ -28,22 +30,34 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
@Override @Override
public ArgType getType(TypeUpdateInfo updateInfo) { public ArgType getType(TypeUpdateInfo updateInfo) {
return getReturnType(updateInfo.getType(invokeNode.getArg(0))); return getReturnType(updateInfo.getType(getInstanceArg()));
} }
@Override @Override
public ArgType getType() { public ArgType getType() {
return getReturnType(invokeNode.getArg(0).getType()); return getReturnType(getInstanceArg().getType());
} }
private ArgType getReturnType(ArgType instanceType) { private ArgType getReturnType(ArgType instanceType) {
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, genericReturnType); ArgType mthDeclType;
IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invokeNode);
if (methodDetails != null) {
// use methods detail to resolve declaration class for virtual invokes
mthDeclType = methodDetails.getMethodInfo().getDeclClass().getType();
} else {
mthDeclType = instanceType;
}
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType);
if (resultGeneric != null && !resultGeneric.isWildcard()) { if (resultGeneric != null && !resultGeneric.isWildcard()) {
return resultGeneric; return resultGeneric;
} }
return invokeNode.getCallMth().getReturnType(); return invokeNode.getCallMth().getReturnType();
} }
private InsnArg getInstanceArg() {
return invokeNode.getArg(0);
}
@Override @Override
public RegisterArg getArg() { public RegisterArg getArg() {
return invokeNode.getResult(); return invokeNode.getResult();
...@@ -71,7 +85,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic { ...@@ -71,7 +85,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
return "InvokeAssign{" + invokeNode.getCallMth().getShortId() return "InvokeAssign{" + invokeNode.getCallMth().getShortId()
+ ", returnType=" + genericReturnType + ", returnType=" + genericReturnType
+ ", currentType=" + getType() + ", currentType=" + getType()
+ ", instanceArg=" + invokeNode.getArg(0) + ", instanceArg=" + getInstanceArg()
+ '}'; + '}';
} }
} }
...@@ -111,7 +111,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { ...@@ -111,7 +111,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
/** /**
* Check if all types resolved * Check if all types resolved
*/ */
private boolean checkTypes(MethodNode mth) { private static boolean checkTypes(MethodNode mth) {
for (SSAVar var : mth.getSVars()) { for (SSAVar var : mth.getSVars()) {
ArgType type = var.getTypeInfo().getType(); ArgType type = var.getTypeInfo().getType();
if (!type.isTypeKnown()) { if (!type.isTypeKnown()) {
...@@ -210,7 +210,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { ...@@ -210,7 +210,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (ssaVar.getTypeInfo().getType().equals(candidateType)) { if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
} else if (candidateType.isTypeKnown()) { } else if (candidateType.isTypeKnown()) {
LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
} }
} }
return false; return false;
...@@ -512,7 +512,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor { ...@@ -512,7 +512,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
private int tryInsertVarCast(MethodNode mth, SSAVar var) { private int tryInsertVarCast(MethodNode mth, SSAVar var) {
for (ITypeBound bound : var.getTypeInfo().getBounds()) { for (ITypeBound bound : var.getTypeInfo().getBounds()) {
ArgType boundType = bound.getType(); ArgType boundType = bound.getType();
if (boundType.isTypeKnown() && boundType.containsTypeVariable()) { if (boundType.isTypeKnown()
&& !boundType.equals(var.getTypeInfo().getType())
&& boundType.containsTypeVariable()
&& !root.getTypeUtils().containsUnknownTypeVar(mth, boundType)) {
if (insertAssignCast(mth, var, boundType)) { if (insertAssignCast(mth, var, boundType)) {
return 1; return 1;
} }
......
...@@ -125,6 +125,16 @@ public final class TypeUpdate { ...@@ -125,6 +125,16 @@ public final class TypeUpdate {
} }
return REJECT; return REJECT;
} }
if (candidateType.containsTypeVariable()) {
// reject unknown type vars
ArgType unknownTypeVar = root.getTypeUtils().checkForUnknownTypeVars(updateInfo.getMth(), candidateType);
if (unknownTypeVar != null) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Type rejected for {}: candidate: '{}' has unknown type var: '{}'", arg, candidateType, unknownTypeVar);
}
return REJECT;
}
}
} }
if (arg instanceof RegisterArg) { if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg; RegisterArg reg = (RegisterArg) arg;
......
...@@ -6,15 +6,13 @@ import java.util.Map; ...@@ -6,15 +6,13 @@ import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest; import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestGenerics2 extends IntegrationTest { public class TestGenerics2 extends IntegrationTest {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
public static class TestCls { public static class TestCls {
public static class ItemReference<V> extends WeakReference<V> { public static class ItemReference<V> extends WeakReference<V> {
public Object id; public Object id;
...@@ -40,22 +38,20 @@ public class TestGenerics2 extends IntegrationTest { ...@@ -40,22 +38,20 @@ public class TestGenerics2 extends IntegrationTest {
@Test @Test
public void test() { public void test() {
ClassNode cls = getClassNode(TestCls.class); assertThat(getClassNode(TestCls.class))
String code = cls.getCode().toString(); .code()
.containsOne("public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {")
assertThat(code, containsString("public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {")); .containsOne("public V get(Object id) {")
assertThat(code, containsString("public V get(Object id) {")); .containsOne("WeakReference<V> ref = ")
assertThat(code, containsString("WeakReference<V> ref = ")); .containsOne("return ref.get();");
assertThat(code, containsString("return ref.get();"));
} }
@Test @Test
@NotYetImplemented("Make generic info propagation for methods (like Map.get)")
public void testDebug() { public void testDebug() {
noDebugInfo(); noDebugInfo();
ClassNode cls = getClassNode(TestCls.class); assertThat(getClassNode(TestCls.class))
String code = cls.getCode().toString(); .code()
.containsOne("ItemReference<V> itemReference = this.items.get(obj);")
assertThat(code, containsString("WeakReference<V> ref = ")); .containsOne("return itemReference.get();");
} }
} }
package jadx.tests.integration.generics;
import java.util.Iterator;
import java.util.LinkedHashMap;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestGenerics8 extends IntegrationTest {
@SuppressWarnings("IllegalType")
public static class TestCls<I> extends LinkedHashMap<I, Integer> implements Iterable<I> {
@Override
public Iterator<I> iterator() {
return keySet().iterator();
}
}
@Test
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("return keySet().iterator();");
}
}
package jadx.tests.integration.generics;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestTypeVarsFromSuperClass extends IntegrationTest {
@SuppressWarnings("ResultOfMethodCallIgnored")
public static class TestCls {
public static class C1<A> {
}
public static class C2<B> extends C1<B> {
public B call() {
return null;
}
}
public static class C3<C> extends C2<C> {
}
public static class C4 extends C3<String> {
public Object test() {
String str = call();
Objects.nonNull(str);
return str;
}
}
}
@Test
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("= call();")
.doesNotContain("(String)");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册