fix: resolve type variables from super types (#870)
This commit is contained in:
@@ -177,7 +177,7 @@ public class ClspGraph {
|
||||
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
||||
if (result.isEmpty()) {
|
||||
Set<String> empty = Collections.emptySet();
|
||||
superTypesCache.put(clsName, result);
|
||||
superTypesCache.put(clsName, empty);
|
||||
return empty;
|
||||
}
|
||||
superTypesCache.put(clsName, result);
|
||||
|
||||
@@ -99,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
return this.methodInfo.compareTo(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toAttrString() {
|
||||
return IMethodDetails.super.toAttrString() + " (c)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -55,6 +55,11 @@ public class SimpleMethodDetails implements IMethodDetails {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toAttrString() {
|
||||
return IMethodDetails.super.toAttrString() + " (s)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
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.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
@@ -58,6 +59,7 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
||||
|
||||
// field
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import jadx.core.Consts;
|
||||
@@ -823,6 +824,7 @@ public abstract class ArgType {
|
||||
* Recursively visit all subtypes of this type.
|
||||
* To exit return non-null value.
|
||||
*/
|
||||
@Nullable
|
||||
public <R> R visitTypes(Function<ArgType, R> visitor) {
|
||||
R r = visitor.apply(this);
|
||||
if (r != null) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,6 +36,7 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -68,11 +70,17 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
private volatile ProcessState state = ProcessState.NOT_LOADED;
|
||||
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();
|
||||
/** Classes which uses this class */
|
||||
/**
|
||||
* Classes which uses this class
|
||||
*/
|
||||
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();
|
||||
|
||||
// cache maps
|
||||
@@ -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() {
|
||||
if (contains(AFlag.DONT_GENERATE)) {
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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.Nullable;
|
||||
@@ -24,7 +27,13 @@ import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
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.utils.TypeUtils;
|
||||
import jadx.core.dex.regions.Region;
|
||||
@@ -680,6 +689,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return mthInfo.compareTo(o.mthInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toAttrString() {
|
||||
return IMethodDetails.super.toAttrString() + " (m)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClass + "." + mthInfo.getName()
|
||||
|
||||
@@ -7,11 +7,13 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
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.NotificationAttrNode;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
@@ -46,6 +48,19 @@ public class TypeUtils {
|
||||
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) {
|
||||
if (type.containsTypeVariable()) {
|
||||
expandTypeVar(cls, type, cls.getGenericTypeParameters());
|
||||
@@ -109,6 +124,26 @@ public class TypeUtils {
|
||||
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
|
||||
* <br>
|
||||
@@ -121,21 +156,31 @@ public class TypeUtils {
|
||||
*/
|
||||
@Nullable
|
||||
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;
|
||||
}
|
||||
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType);
|
||||
if (replaceMap.isEmpty()) {
|
||||
Map<ArgType, ArgType> typeVarsMap;
|
||||
ClassTypeVarsAttr typeVars = getClassTypeVars(instanceType);
|
||||
if (typeVars != null) {
|
||||
typeVarsMap = typeVars.getSuperTypeMaps().get(genericSourceType.getObject());
|
||||
} else {
|
||||
typeVarsMap = getTypeVariablesMapping(instanceType);
|
||||
}
|
||||
if (typeVarsMap == null) {
|
||||
return null;
|
||||
}
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap);
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, typeVarsMap);
|
||||
}
|
||||
|
||||
public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
|
||||
if (!clsType.isGeneric()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
List<ArgType> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
|
||||
if (typeParameters.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
@@ -239,4 +284,50 @@ public class TypeUtils {
|
||||
}
|
||||
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 {
|
||||
this.accFlags = accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toAttrString() {
|
||||
return IMethodDetails.super.toAttrString() + " (mut)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mutable" + toAttrString();
|
||||
|
||||
+18
-4
@@ -2,7 +2,9 @@ package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
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.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
/**
|
||||
@@ -28,22 +30,34 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
||||
|
||||
@Override
|
||||
public ArgType getType(TypeUpdateInfo updateInfo) {
|
||||
return getReturnType(updateInfo.getType(invokeNode.getArg(0)));
|
||||
return getReturnType(updateInfo.getType(getInstanceArg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getType() {
|
||||
return getReturnType(invokeNode.getArg(0).getType());
|
||||
return getReturnType(getInstanceArg().getType());
|
||||
}
|
||||
|
||||
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()) {
|
||||
return resultGeneric;
|
||||
}
|
||||
return invokeNode.getCallMth().getReturnType();
|
||||
}
|
||||
|
||||
private InsnArg getInstanceArg() {
|
||||
return invokeNode.getArg(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisterArg getArg() {
|
||||
return invokeNode.getResult();
|
||||
@@ -71,7 +85,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
||||
return "InvokeAssign{" + invokeNode.getCallMth().getShortId()
|
||||
+ ", returnType=" + genericReturnType
|
||||
+ ", currentType=" + getType()
|
||||
+ ", instanceArg=" + invokeNode.getArg(0)
|
||||
+ ", instanceArg=" + getInstanceArg()
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -111,7 +111,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
/**
|
||||
* Check if all types resolved
|
||||
*/
|
||||
private boolean checkTypes(MethodNode mth) {
|
||||
private static boolean checkTypes(MethodNode mth) {
|
||||
for (SSAVar var : mth.getSVars()) {
|
||||
ArgType type = var.getTypeInfo().getType();
|
||||
if (!type.isTypeKnown()) {
|
||||
@@ -210,7 +210,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
|
||||
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||
} else if (candidateType.isTypeKnown()) {
|
||||
LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||
LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -512,7 +512,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
private int tryInsertVarCast(MethodNode mth, SSAVar var) {
|
||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||
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)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,16 @@ public final class TypeUpdate {
|
||||
}
|
||||
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) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
|
||||
@@ -6,15 +6,13 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestGenerics2 extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||
public static class TestCls {
|
||||
public static class ItemReference<V> extends WeakReference<V> {
|
||||
public Object id;
|
||||
@@ -40,22 +38,20 @@ public class TestGenerics2 extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {"));
|
||||
assertThat(code, containsString("public V get(Object id) {"));
|
||||
assertThat(code, containsString("WeakReference<V> ref = "));
|
||||
assertThat(code, containsString("return ref.get();"));
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {")
|
||||
.containsOne("public V get(Object id) {")
|
||||
.containsOne("WeakReference<V> ref = ")
|
||||
.containsOne("return ref.get();");
|
||||
}
|
||||
|
||||
@Test
|
||||
@NotYetImplemented("Make generic info propagation for methods (like Map.get)")
|
||||
public void testDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("WeakReference<V> ref = "));
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("ItemReference<V> itemReference = this.items.get(obj);")
|
||||
.containsOne("return itemReference.get();");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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();");
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
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)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user