fix: handle wildcard in invoke type resolver (#1238)
This commit is contained in:
@@ -192,6 +192,11 @@ public class SSAVar {
|
||||
return usedInPhi;
|
||||
}
|
||||
|
||||
public boolean isAssignInPhi() {
|
||||
InsnNode assignInsn = getAssignInsn();
|
||||
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
|
||||
}
|
||||
|
||||
public boolean isUsedInPhi() {
|
||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ public class MoveInlineVisitor extends AbstractVisitor {
|
||||
if (resultArg.sameRegAndSVar(moveArg)) {
|
||||
return true;
|
||||
}
|
||||
if (moveArg.isRegister()) {
|
||||
RegisterArg moveReg = (RegisterArg) moveArg;
|
||||
if (moveReg.getSVar().isAssignInPhi()) {
|
||||
// don't mix already merged variables
|
||||
return false;
|
||||
}
|
||||
}
|
||||
SSAVar ssaVar = resultArg.getSVar();
|
||||
if (ssaVar.isUsedInPhi()) {
|
||||
return deleteMove(mth, move);
|
||||
|
||||
+4
@@ -46,6 +46,10 @@ public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
||||
return insn.getResult();
|
||||
}
|
||||
|
||||
public IndexInsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}";
|
||||
|
||||
+16
-2
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
@@ -48,12 +50,24 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
||||
mthDeclType = instanceType;
|
||||
}
|
||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType);
|
||||
if (resultGeneric != null && !resultGeneric.isWildcard()) {
|
||||
return resultGeneric;
|
||||
ArgType result = processResultType(resultGeneric);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return invokeNode.getCallMth().getReturnType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArgType processResultType(@Nullable ArgType resultGeneric) {
|
||||
if (resultGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
if (!resultGeneric.isWildcard()) {
|
||||
return resultGeneric;
|
||||
}
|
||||
return resultGeneric.getWildcardType();
|
||||
}
|
||||
|
||||
private InsnArg getInstanceArg() {
|
||||
return invokeNode.getArg(0);
|
||||
}
|
||||
|
||||
+56
-1
@@ -57,6 +57,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
@@ -83,6 +84,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
this.resolvers = Arrays.asList(
|
||||
this::initTypeBounds,
|
||||
this::runTypePropagation,
|
||||
this::tryRestoreTypeVarCasts,
|
||||
this::tryInsertCasts,
|
||||
this::tryDeduceTypes,
|
||||
this::trySplitConstInsns,
|
||||
@@ -520,7 +522,60 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ForLoopReplaceableByWhile")
|
||||
/**
|
||||
* Fix check casts to type var extend type:
|
||||
* <br>
|
||||
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
||||
*/
|
||||
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
||||
int changed = 0;
|
||||
List<SSAVar> mthSVars = mth.getSVars();
|
||||
for (SSAVar var : mthSVars) {
|
||||
changed += restoreTypeVarCasts(var);
|
||||
}
|
||||
if (changed == 0) {
|
||||
return false;
|
||||
}
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
mth.addDebugComment("Restore " + changed + " type vars casts");
|
||||
}
|
||||
initTypeBounds(mth);
|
||||
return runTypePropagation(mth);
|
||||
}
|
||||
|
||||
private int restoreTypeVarCasts(SSAVar var) {
|
||||
TypeInfo typeInfo = var.getTypeInfo();
|
||||
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
||||
return 0;
|
||||
}
|
||||
List<ITypeBound> casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance);
|
||||
if (casts.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN);
|
||||
if (!bestType.isGenericType()) {
|
||||
return 0;
|
||||
}
|
||||
List<ArgType> extendTypes = bestType.getExtendTypes();
|
||||
if (extendTypes.size() != 1) {
|
||||
return 0;
|
||||
}
|
||||
int fixed = 0;
|
||||
ArgType extendType = extendTypes.get(0);
|
||||
for (ITypeBound bound : casts) {
|
||||
TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound;
|
||||
ArgType castType = cast.getType();
|
||||
TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType);
|
||||
if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||
cast.getInsn().updateIndex(bestType);
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" })
|
||||
private boolean tryInsertCasts(MethodNode mth) {
|
||||
int added = 0;
|
||||
List<SSAVar> mthSVars = mth.getSVars();
|
||||
|
||||
@@ -112,7 +112,7 @@ public class ListUtils {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <T> List<T> filter(List<T> list, Predicate<T> filter) {
|
||||
public static <T> List<T> filter(Collection<T> list, Predicate<T> filter) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class ListUtils {
|
||||
return found;
|
||||
}
|
||||
|
||||
public static <T> boolean allMatch(List<T> list, Predicate<T> test) {
|
||||
public static <T> boolean allMatch(Collection<T> list, Predicate<T> test) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ public class ListUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static <T> boolean anyMatch(List<T> list, Predicate<T> test) {
|
||||
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package jadx.tests.integration.types;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
/**
|
||||
* Issue 1238
|
||||
*/
|
||||
public class TestTypeResolver20 extends SmaliTest {
|
||||
|
||||
public static class TestCls {
|
||||
public interface Sequence<T> {
|
||||
Iterator<T> iterator();
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> T max(Sequence<? extends T> seq) {
|
||||
Iterator<? extends T> it = seq.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
T t = it.next();
|
||||
while (it.hasNext()) {
|
||||
T next = it.next();
|
||||
if (t.compareTo(next) < 0) {
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
private static class ArraySeq<T> implements Sequence<T> {
|
||||
private final List<T> list;
|
||||
|
||||
@SafeVarargs
|
||||
public ArraySeq(T... arr) {
|
||||
this.list = Arrays.asList(arr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(max(new ArraySeq<>(2, 5, 3, 4))).isEqualTo(5);
|
||||
}
|
||||
}
|
||||
|
||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.doesNotContain("next = next;")
|
||||
.containsOne("T next = it.next();");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmali() {
|
||||
assertThat(getClassNodeFromSmaliFiles())
|
||||
.code()
|
||||
.containsOne("T next = it.next();")
|
||||
.containsOne("T next2 = it.next();");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
.class public interface abstract Lkotlin/sequences/Sequence;
|
||||
.super Ljava/lang/Object;
|
||||
.source "SourceFile"
|
||||
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"<T:",
|
||||
"Ljava/lang/Object;",
|
||||
">",
|
||||
"Ljava/lang/Object;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.method public abstract iterator()Ljava/util/Iterator;
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"()",
|
||||
"Ljava/util/Iterator<",
|
||||
"TT;>;"
|
||||
}
|
||||
.end annotation
|
||||
.end method
|
||||
@@ -0,0 +1,61 @@
|
||||
.class public Ltypes/TestTypeResolver20;
|
||||
.super Ljava/lang/Object;
|
||||
.source "SourceFile"
|
||||
|
||||
|
||||
.method public static final max(Lkotlin/sequences/Sequence;)Ljava/lang/Comparable;
|
||||
.registers 4
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"<T::",
|
||||
"Ljava/lang/Comparable<",
|
||||
"-TT;>;>(",
|
||||
"Lkotlin/sequences/Sequence<",
|
||||
"+TT;>;)TT;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.line 1147
|
||||
invoke-interface {p0}, Lkotlin/sequences/Sequence;->iterator()Ljava/util/Iterator;
|
||||
move-result-object p0
|
||||
|
||||
.line 1148
|
||||
invoke-interface {p0}, Ljava/util/Iterator;->hasNext()Z
|
||||
move-result v0
|
||||
|
||||
if-nez v0, :cond_11
|
||||
|
||||
const/4 p0, 0x0
|
||||
return-object p0
|
||||
|
||||
.line 1149
|
||||
:cond_11
|
||||
invoke-interface {p0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||
move-result-object v0
|
||||
check-cast v0, Ljava/lang/Comparable;
|
||||
|
||||
.line 1150
|
||||
:cond_17
|
||||
:goto_17
|
||||
invoke-interface {p0}, Ljava/util/Iterator;->hasNext()Z
|
||||
move-result v1
|
||||
|
||||
if-eqz v1, :cond_2b
|
||||
|
||||
.line 1151
|
||||
invoke-interface {p0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||
move-result-object v1
|
||||
check-cast v1, Ljava/lang/Comparable;
|
||||
|
||||
.line 1152
|
||||
invoke-interface {v0, v1}, Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I
|
||||
move-result v2
|
||||
|
||||
if-gez v2, :cond_17
|
||||
|
||||
move-object v0, v1
|
||||
goto :goto_17
|
||||
|
||||
:cond_2b
|
||||
return-object v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user