fix: handle unbound type variables in type inference (#1237)

This commit is contained in:
Skylot
2021-08-24 13:32:06 +01:00
parent 5af60b2ff4
commit a62de839be
5 changed files with 67 additions and 18 deletions
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -16,8 +17,6 @@ import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import static java.util.Collections.unmodifiableList;
public class SignatureProcessor extends AbstractVisitor {
private RootNode root;
@@ -106,25 +105,38 @@ public class SignatureProcessor extends AbstractVisitor {
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
ArgType parsedRetType = sp.consumeType();
if (!validateParsedType(parsedRetType, mth.getMethodInfo().getReturnType())) {
mth.addWarnComment("Incorrect return type in method signature: " + sp.getSignature());
return;
}
List<ArgType> checkedArgTypes = checkArgTypes(mth, sp, parsedArgTypes);
if (checkedArgTypes == null) {
return;
}
mth.updateTypeParameters(typeParameters); // apply before expand args
TypeUtils typeUtils = root.getTypeUtils();
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
List<ArgType> resultArgTypes = Utils.collectionMap(checkedArgTypes, t -> typeUtils.expandTypeVariables(mth, t));
mth.updateTypes(unmodifiableList(resultArgTypes), retType);
List<ArgType> argTypes = Utils.collectionMap(parsedArgTypes, t -> typeUtils.expandTypeVariables(mth, t));
if (!validateAndApplyTypes(mth, sp, retType, argTypes)) {
// bad types -> reset typed parameters
mth.updateTypeParameters(Collections.emptyList());
}
} catch (Exception e) {
mth.addWarnComment("Failed to parse method signature: " + sp.getSignature(), e);
}
}
private boolean validateAndApplyTypes(MethodNode mth, SignatureParser sp, ArgType retType, List<ArgType> argTypes) {
try {
if (!validateParsedType(retType, mth.getMethodInfo().getReturnType())) {
mth.addWarnComment("Incorrect return type in method signature: " + sp.getSignature());
return false;
}
List<ArgType> checkedArgTypes = checkArgTypes(mth, sp, argTypes);
if (checkedArgTypes == null) {
return false;
}
mth.updateTypes(Utils.lockList(checkedArgTypes), retType);
return true;
} catch (Exception e) {
mth.addWarnComment("Type validation failed for signature: " + sp.getSignature(), e);
return false;
}
}
private List<ArgType> checkArgTypes(MethodNode mth, SignatureParser sp, List<ArgType> parsedArgTypes) {
MethodInfo mthInfo = mth.getMethodInfo();
List<ArgType> mthArgTypes = mthInfo.getArgumentsTypes();
@@ -245,12 +245,12 @@ public class TypeCompare {
if (objType.isGenericType()) {
return compareTypeVariables(genericType, objType);
}
boolean rootObject = objType.equals(ArgType.OBJECT);
List<ArgType> extendTypes = genericType.getExtendTypes();
if (extendTypes.isEmpty()) {
return NARROW;
return rootObject ? NARROW : CONFLICT;
}
if (extendTypes.contains(objType) || objType.equals(ArgType.OBJECT)) {
if (extendTypes.contains(objType) || rootObject) {
return NARROW;
}
for (ArgType extendType : extendTypes) {
@@ -138,7 +138,7 @@ public class TypeCompareTest {
public void compareGenericTypes() {
ArgType vType = genericType("V");
check(vType, OBJECT, TypeCompareEnum.NARROW);
check(vType, STRING, TypeCompareEnum.NARROW);
check(vType, STRING, TypeCompareEnum.CONFLICT);
ArgType rType = genericType("R");
check(vType, rType, TypeCompareEnum.CONFLICT);
@@ -0,0 +1,37 @@
package jadx.tests.integration.types;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestTypeResolver18 extends IntegrationTest {
public static class TestCls<T> {
private final AtomicReference<T> reference = new AtomicReference<>();
public void test() {
T t = this.reference.get();
if (t instanceof Closeable) {
try {
((Closeable) t).close();
} catch (IOException unused) {
// ignore
}
}
this.reference.set(null);
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("((Closeable) t).close();");
}
}
@@ -12,7 +12,7 @@
"Lapp/ItemCurrency;",
"Lapp/ItemCurrency;",
"Lapp/ItemCurrency;",
"ZTItem;)Z"
"ZLapp/SearchListItem;)Z"
}
.end annotation