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

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

上级 cf1d9e83
......@@ -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<>();
......
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;
......
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();
......
......@@ -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()
+ '}';
}
}
......@@ -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();");
}
}
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.
先完成此消息的编辑!
想要评论请 注册