From 0541748e5feea66618c3d2eacdfde6f6cb38bdc3 Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 3 Jun 2021 18:48:30 +0100 Subject: [PATCH] fix: resolve type variables from super types (#870) --- .../main/java/jadx/core/clsp/ClspGraph.java | 2 +- .../main/java/jadx/core/clsp/ClspMethod.java | 5 + .../jadx/core/clsp/SimpleMethodDetails.java | 5 + .../java/jadx/core/dex/attributes/AType.java | 2 + .../attributes/nodes/ClassTypeVarsAttr.java | 47 ++++++++ .../core/dex/instructions/args/ArgType.java | 2 + .../java/jadx/core/dex/nodes/ClassNode.java | 27 ++++- .../java/jadx/core/dex/nodes/MethodNode.java | 18 +++- .../jadx/core/dex/nodes/utils/TypeUtils.java | 101 +++++++++++++++++- .../methods/MutableMethodDetails.java | 5 + .../typeinference/TypeBoundInvokeAssign.java | 22 +++- .../typeinference/TypeInferenceVisitor.java | 9 +- .../visitors/typeinference/TypeUpdate.java | 10 ++ .../integration/generics/TestGenerics2.java | 28 +++-- .../integration/generics/TestGenerics8.java | 29 +++++ .../generics/TestTypeVarsFromSuperClass.java | 45 ++++++++ 16 files changed, 323 insertions(+), 34 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics8.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromSuperClass.java diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index dab33a9f..d6e3ee79 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -177,7 +177,7 @@ public class ClspGraph { private Set putInSuperTypesCache(String clsName, Set result) { if (result.isEmpty()) { Set empty = Collections.emptySet(); - superTypesCache.put(clsName, result); + superTypesCache.put(clsName, empty); return empty; } superTypesCache.put(clsName, result); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java index 0c88fc51..ff02f5cf 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java @@ -99,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable { return this.methodInfo.compareTo(other.methodInfo); } + @Override + public String toAttrString() { + return IMethodDetails.super.toAttrString() + " (c)"; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java index 63086900..669324f2 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java @@ -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 + '}'; diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 2e91aa0f..f0486ef9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -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 { public static final AType SOURCE_FILE = new AType<>(); public static final AType ENUM_CLASS = new AType<>(); public static final AType ENUM_MAP = new AType<>(); + public static final AType CLASS_TYPE_VARS = new AType<>(); // field public static final AType FIELD_INIT = new AType<>(); 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 new file mode 100644 index 00000000..7cfe2027 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java @@ -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 typeVars; + + /** + * Type vars mapping in current and super types: + * TypeRawObj -> (TypeVarInSuperType -> TypeVarFromThisClass) + */ + private final Map> superTypeMaps; + + public ClassTypeVarsAttr(List typeVars, Map> superTypeMaps) { + this.typeVars = typeVars; + this.superTypeMaps = superTypeMaps; + } + + public List getTypeVars() { + return typeVars; + } + + public Map> getSuperTypeMaps() { + return superTypeMaps; + } + + @Override + public AType getType() { + return AType.CLASS_TYPE_VARS; + } + + @Override + public String toString() { + return "ClassTypeVarsAttr{" + typeVars + ", super maps: " + superTypeMaps + '}'; + } +} 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 64b7e883..25d4d829 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 @@ -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 visitTypes(Function visitor) { R r = visitor.apply(this); if (r != null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 30c9c845..9c2dfb95 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -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 dependencies = Collections.emptyList(); - /** Classes which uses this class */ + /** + * Classes which uses this class + */ private List 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 useInMth = Collections.emptyList(); // cache maps @@ -429,6 +437,19 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } } + public void visitSuperTypes(BiConsumer 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; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index ab7696d7..85d94fa6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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() 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 a6b7f584..f8c2e8a5 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 @@ -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 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 *
@@ -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 replaceMap = getTypeVariablesMapping(instanceType); - if (replaceMap.isEmpty()) { + Map 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 getTypeVariablesMapping(ArgType clsType) { if (!clsType.isGeneric()) { return Collections.emptyMap(); } - List 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> map = new HashMap<>(); + ArgType currentClsType = cls.getClassInfo().getType(); + map.put(currentClsType.getObject(), getTypeVariablesMapping(currentClsType)); + + cls.visitSuperTypes((parent, type) -> { + List currentVars = type.getGenericTypes(); + if (Utils.isEmpty(currentVars)) { + return; + } + int varsCount = currentVars.size(); + List sourceTypeVars = getClassGenerics(type); + if (varsCount == sourceTypeVars.size()) { + Map parentTypeMap = map.get(parent.getObject()); + Map 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 currentTypeVars = cls.getGenericTypeParameters(); + ClassTypeVarsAttr typeVarsAttr = new ClassTypeVarsAttr(currentTypeVars, map); + cls.addAttr(typeVarsAttr); + return typeVarsAttr; + } + + public void visitSuperTypes(ArgType type, BiConsumer 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); + } + } + } + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java index 0b7f6057..275d35c3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java index 8a4f7749..d391ab79 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java @@ -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() + '}'; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 7e166f4b..8605f405 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index b2a92667..30a0ca15 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -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; diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java index 45512c32..e63a391e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java @@ -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 extends WeakReference { 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 queue) {")); - assertThat(code, containsString("public V get(Object id) {")); - assertThat(code, containsString("WeakReference ref = ")); - assertThat(code, containsString("return ref.get();")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("public ItemReference(V item, Object objId, ReferenceQueue queue) {") + .containsOne("public V get(Object id) {") + .containsOne("WeakReference 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 ref = ")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("ItemReference itemReference = this.items.get(obj);") + .containsOne("return itemReference.get();"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics8.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics8.java new file mode 100644 index 00000000..84d390bd --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics8.java @@ -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 extends LinkedHashMap implements Iterable { + @Override + public Iterator iterator() { + return keySet().iterator(); + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return keySet().iterator();"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromSuperClass.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromSuperClass.java new file mode 100644 index 00000000..32372bce --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestTypeVarsFromSuperClass.java @@ -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 { + } + + public static class C2 extends C1 { + public B call() { + return null; + } + } + + public static class C3 extends C2 { + } + + public static class C4 extends C3 { + 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)"); + } +} -- GitLab