From ba17f7bc00a15daf16f14c83c1e410357c94097c Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 15 Aug 2019 21:43:57 +0100 Subject: [PATCH] refactor: move type with outer generic to different class --- .../main/java/jadx/core/codegen/ClassGen.java | 8 ++ .../core/dex/instructions/args/ArgType.java | 77 ++++++++++++++----- .../dex/nodes/parser/SignatureParser.java | 2 +- .../dex/instructions/args/ArgTypeTest.java | 19 +++++ .../tests/functional/SignatureParserTest.java | 10 +-- .../generics/TestOuterGeneric.java | 72 +++++++++++++++++ 6 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestOuterGeneric.java diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 01942264a..af6cf07cd 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -458,6 +458,14 @@ public class ClassGen { } public void useClass(CodeWriter code, ArgType type) { + ArgType outerType = type.getOuterType(); + if (outerType != null) { + useClass(code, outerType); + code.add('.'); + useClass(code, type.getInnerType()); + return; + } + useClass(code, ClassInfo.fromType(cls.root(), type)); ArgType[] generics = type.getGenericTypes(); if (generics != null) { 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 7f8a3cb32..783e147d5 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 @@ -3,6 +3,7 @@ package jadx.core.dex.instructions.args; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import jadx.core.Consts; import jadx.core.dex.info.ClassInfo; @@ -89,8 +90,8 @@ public abstract class ArgType { return new GenericObject(obj, generics); } - public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) { - return new GenericObject((GenericObject) genericType, innerName, generics); + public static ArgType outerGeneric(ArgType genericOuterType, ArgType innerType) { + return new OuterGenericObject((GenericObject) genericOuterType, (ObjectType) innerType); } public static ArgType array(ArgType vtype) { @@ -244,8 +245,8 @@ public abstract class ArgType { public WildcardType(ArgType obj, WildcardBound bound) { super(OBJECT.getObject()); - this.type = obj; - this.bound = bound; + this.type = Objects.requireNonNull(obj); + this.bound = Objects.requireNonNull(bound); } @Override @@ -281,19 +282,10 @@ public abstract class ArgType { private static class GenericObject extends ObjectType { private final ArgType[] generics; - private final GenericObject outerType; public GenericObject(String obj, ArgType[] generics) { super(obj); - this.outerType = null; - this.generics = generics; - this.hash = calcHash(); - } - - public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) { - super(outerType.getObject() + '$' + innerName); - this.outerType = outerType; - this.generics = generics; + this.generics = Objects.requireNonNull(generics); this.hash = calcHash(); } @@ -311,11 +303,6 @@ public abstract class ArgType { return generics; } - @Override - public ArgType getOuterType() { - return outerType; - } - @Override boolean internalEquals(Object obj) { return super.internalEquals(obj) @@ -328,6 +315,54 @@ public abstract class ArgType { } } + private static class OuterGenericObject extends ObjectType { + private final GenericObject outerType; + private final ObjectType innerType; + + public OuterGenericObject(GenericObject outerType, ObjectType innerType) { + super(outerType.getObject() + '$' + innerType.getObject()); + this.outerType = outerType; + this.innerType = innerType; + this.hash = calcHash(); + } + + private int calcHash() { + return objName.hashCode() + 31 * (outerType.hashCode() + 31 * innerType.hashCode()); + } + + @Override + public boolean isGeneric() { + return true; + } + + @Override + public ArgType[] getGenericTypes() { + return innerType.getGenericTypes(); + } + + @Override + public ArgType getOuterType() { + return outerType; + } + + @Override + public ArgType getInnerType() { + return innerType; + } + + @Override + boolean internalEquals(Object obj) { + return super.internalEquals(obj) + && Objects.equals(outerType, ((OuterGenericObject) obj).outerType) + && Objects.equals(innerType, ((OuterGenericObject) obj).innerType); + } + + @Override + public String toString() { + return outerType.toString() + '$' + innerType.toString(); + } + } + private static final class ArrayArg extends KnownType { private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY }; private final ArgType arrayElement; @@ -494,6 +529,10 @@ public abstract class ArgType { return null; } + public ArgType getInnerType() { + return null; + } + public boolean isArray() { return false; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java index d9de3d247..2c44ad60a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -184,7 +184,7 @@ public class SignatureParser { if (inner == null) { throw new JadxRuntimeException("No inner type found: " + debugString()); } - return ArgType.genericInner(genericType, inner.getObject(), inner.getGenericTypes()); + return ArgType.outerGeneric(genericType, inner); } else { consume(';'); return genericType; diff --git a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java index fbc5d9f01..fbed8cad4 100644 --- a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java @@ -1,12 +1,18 @@ package jadx.core.dex.instructions.args; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class ArgTypeTest { + private static final Logger LOG = LoggerFactory.getLogger(ArgTypeTest.class); + @Test public void testEqualsOfGenericTypes() { ArgType first = ArgType.generic("java.lang.List", ArgType.STRING); @@ -22,6 +28,19 @@ class ArgTypeTest { ArgType type = ArgType.generic("java.lang.List", wildcard); assertTrue(type.containsGenericType()); + } + @Test + void testInnerGeneric() { + ArgType[] genericTypes = new ArgType[] { ArgType.genericType("K"), ArgType.genericType("V") }; + ArgType base = ArgType.generic("java.util.Map", genericTypes); + + ArgType genericInner = ArgType.outerGeneric(base, ArgType.generic("Entry", genericTypes)); + LOG.debug("genericInner : {}", genericInner); + + ArgType genericInner2 = ArgType.outerGeneric(base, ArgType.object("Entry")); + LOG.debug("genericInner2: {}", genericInner2); + + assertThat(genericInner.toString(), is("java.util.Map$Entry")); } } diff --git a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java index 5c59c1e76..116023bea 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java @@ -14,9 +14,9 @@ import static jadx.core.dex.instructions.args.ArgType.INT; import static jadx.core.dex.instructions.args.ArgType.OBJECT; import static jadx.core.dex.instructions.args.ArgType.array; import static jadx.core.dex.instructions.args.ArgType.generic; -import static jadx.core.dex.instructions.args.ArgType.genericInner; import static jadx.core.dex.instructions.args.ArgType.genericType; import static jadx.core.dex.instructions.args.ArgType.object; +import static jadx.core.dex.instructions.args.ArgType.outerGeneric; import static jadx.core.dex.instructions.args.ArgType.wildcard; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -46,9 +46,9 @@ class SignatureParserTest { checkType("La;", generic("La;", genericType("V"), object("b"))); checkType("La;>;", generic("La;", generic("Lb;", object("Lc;")))); checkType("La/b/C;>;", generic("La/b/C;", generic("Ld/E;", object("Lf/G;")))); - checkType("La.c;", genericInner(generic("La;", genericType("D")), "c", null)); - checkType("La.c/d;", genericInner(generic("La;", genericType("D")), "c.d", null)); - checkType("La.c;", genericInner(generic("La;", object("Lb;")), "c", new ArgType[] { genericType("V") })); + checkType("La.c;", outerGeneric(generic("La;", genericType("D")), ArgType.object("c"))); + checkType("La.c/d;", outerGeneric(generic("La;", genericType("D")), ArgType.object("c.d"))); + checkType("La.c;", outerGeneric(generic("La;", object("Lb;")), ArgType.generic("c", genericType("V")))); } @Test @@ -117,7 +117,7 @@ class SignatureParserTest { assertThat(argTypes, hasSize(1)); ArgType argType = argTypes.get(0); assertThat(argType.getObject().indexOf('/'), is(-1)); - assertThat(argType, is(genericInner(generic("La/b/C;", genericType("T")), "d.E", null))); + assertThat(argType, is(outerGeneric(generic("La/b/C;", genericType("T")), object("d.E")))); } @Test diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestOuterGeneric.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestOuterGeneric.java new file mode 100644 index 000000000..70c97e5a5 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestOuterGeneric.java @@ -0,0 +1,72 @@ +package jadx.tests.integration.generics; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import jadx.NotYetImplemented; +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestOuterGeneric extends IntegrationTest { + + public static class TestCls { + public static class A { + public class B { + } + + public class C { + } + } + + public static class D { + public class E { + } + } + + public void test1() { + A a = new A<>(); + use(a); + A.B b = a.new B(); + use(b); + use(b); + A.C c = a.new C(); + use(c); + use(c); + + use(new A>().new C()); + } + + public void test2() { + D d = new D(); + D.E e = d.new E(); + use(e); + use(e); + } + + public void test3() { + use(A.class); + use(A.B.class); + use(A.C.class); + } + + private void use(Object obj) { + } + } + + @NotYetImplemented("Instance constructor for inner classes") + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("A a = new A<>();")); + assertThat(code, containsOne("A.B b = a.new B();")); + assertThat(code, containsOne("A.C c = a.new C();")); + assertThat(code, containsOne("use(new A>().new C());")); + assertThat(code, containsOne("D.E e = d.new E();")); + } +}