From 2acc14b04aa60cfdce85e37524e067a55e68220a Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 30 May 2021 10:04:59 +0100 Subject: [PATCH] fix: resolve generic type vars for instance field get instruction (#918) --- .../jadx/core/dex/nodes/utils/TypeUtils.java | 3 + .../TypeBoundFieldGetAssign.java | 83 +++++++++++++++++++ .../typeinference/TypeInferenceVisitor.java | 12 +++ .../generics/TestGenericFields.java | 40 +++++++++ 4 files changed, 138 insertions(+) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericFields.java 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 3cf60bbab..a6b7f5846 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 @@ -61,6 +61,9 @@ public class TypeUtils { } private void expandTypeVar(NotificationAttrNode node, ArgType type, Collection typeVars) { + if (typeVars.isEmpty()) { + return; + } boolean allExtendsEmpty = true; for (ArgType argType : typeVars) { if (notEmpty(argType.getExtendTypes())) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java new file mode 100644 index 000000000..9d29429f3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java @@ -0,0 +1,83 @@ +package jadx.core.dex.visitors.typeinference; + +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.instructions.IndexInsnNode; +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.RootNode; + +/** + * Dynamic bound for instance field get of generic type. + * Bound type calculated using instance generic type. + */ +public final class TypeBoundFieldGetAssign implements ITypeBoundDynamic { + private final RootNode root; + private final IndexInsnNode getNode; + private final FieldInfo fieldInfo; + private final ArgType initType; + + public TypeBoundFieldGetAssign(RootNode root, IndexInsnNode getNode, ArgType initType) { + this.root = root; + this.getNode = getNode; + this.fieldInfo = ((FieldInfo) getNode.getIndex()); + this.initType = initType; + } + + @Override + public BoundEnum getBound() { + return BoundEnum.ASSIGN; + } + + @Override + public ArgType getType(TypeUpdateInfo updateInfo) { + return getResultType(updateInfo.getType(getInstanceArg())); + } + + @Override + public ArgType getType() { + return getResultType(getInstanceArg().getType()); + } + + private ArgType getResultType(ArgType instanceType) { + ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, initType); + if (resultGeneric != null && !resultGeneric.isWildcard()) { + return resultGeneric; + } + return initType; // TODO: check if this type is allowed in current scope + } + + private InsnArg getInstanceArg() { + return getNode.getArg(0); + } + + @Override + public RegisterArg getArg() { + return getNode.getResult(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TypeBoundFieldGetAssign that = (TypeBoundFieldGetAssign) o; + return getNode.equals(that.getNode); + } + + @Override + public int hashCode() { + return getNode.hashCode(); + } + + @Override + public String toString() { + return "FieldGetAssign{" + fieldInfo + + ", type=" + getType() + + ", 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 873011b74..7e166f4b9 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 @@ -293,6 +293,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor { addBound(typeInfo, makeAssignInvokeBound((InvokeNode) insn)); break; + case IGET: + addBound(typeInfo, makeAssignFieldGetBound((IndexInsnNode) insn)); + break; + case CHECK_CAST: addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn)); break; @@ -304,6 +308,14 @@ public final class TypeInferenceVisitor extends AbstractVisitor { } } + private ITypeBound makeAssignFieldGetBound(IndexInsnNode insn) { + ArgType initType = insn.getResult().getInitType(); + if (initType.containsTypeVariable()) { + return new TypeBoundFieldGetAssign(root, insn, initType); + } + return new TypeBoundConst(BoundEnum.ASSIGN, initType); + } + private ITypeBound makeAssignInvokeBound(InvokeNode invokeNode) { ArgType boundType = invokeNode.getCallMth().getReturnType(); ArgType genericReturnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode); diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericFields.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericFields.java new file mode 100644 index 000000000..d388cccca --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericFields.java @@ -0,0 +1,40 @@ +package jadx.tests.integration.generics; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestGenericFields extends IntegrationTest { + + public static class TestCls { + + public static class Summary { + Value price; + } + + public static class Value { + T value; + } + + public static class Amount { + String cur; + int val; + } + + public String test(Summary summary) { + Amount amount = summary.price.value; + return amount.val + " " + amount.cur; + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("T t = ") + .containsOne("Amount amount ="); + } +}