From f5767dd865e597045f56745e8bd992d7a90e669e Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 16 Jul 2020 14:14:06 +0100 Subject: [PATCH] fix: use recursive objects for nested inner generic classes (#869) --- .../main/java/jadx/core/codegen/ClassGen.java | 12 +++- .../core/dex/instructions/args/ArgType.java | 6 +- .../dex/nodes/parser/SignatureParser.java | 58 +++++++++++-------- .../tests/functional/SignatureParserTest.java | 11 ++++ .../deobf/TestFieldFromInnerClass.java | 41 +++++++++++++ 5 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/deobf/TestFieldFromInnerClass.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 86542eb1d..c42389411 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -511,7 +511,8 @@ public class ClassGen { if (outerType != null) { useClass(code, outerType); code.add('.'); - useClass(code, type.getInnerType()); + // import not needed, force use short name + useClassShortName(code, type.getObject()); return; } @@ -540,6 +541,15 @@ public class ClassGen { } } + private void useClassShortName(CodeWriter code, String object) { + ClassInfo classInfo = ClassInfo.fromName(cls.root(), object); + ClassNode classNode = cls.root().resolveClass(classInfo); + if (classNode != null) { + code.attachAnnotation(classNode); + } + code.add(classInfo.getAliasShortName()); + } + public void useClass(CodeWriter code, ClassInfo classInfo) { ClassNode classNode = cls.root().resolveClass(classInfo); if (classNode != 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 688a075e4..1b335756b 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 @@ -116,7 +116,7 @@ public abstract class ArgType { } public static ArgType outerGeneric(ArgType genericOuterType, ArgType innerType) { - return new OuterGenericObject((GenericObject) genericOuterType, (ObjectType) innerType); + return new OuterGenericObject((ObjectType) genericOuterType, (ObjectType) innerType); } public static ArgType array(@NotNull ArgType vtype) { @@ -341,10 +341,10 @@ public abstract class ArgType { } private static class OuterGenericObject extends ObjectType { - private final GenericObject outerType; + private final ObjectType outerType; private final ObjectType innerType; - public OuterGenericObject(GenericObject outerType, ObjectType innerType) { + public OuterGenericObject(ObjectType outerType, ObjectType innerType) { super(outerType.getObject() + '$' + innerType.getObject()); this.outerType = outerType; this.innerType = innerType; 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 870aa32b5..a9a6befd9 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 @@ -173,10 +173,14 @@ public class SignatureParser { throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch); } - private ArgType consumeObjectType(boolean incompleteType) { + private ArgType consumeObjectType(boolean innerType) { mark(); int ch; do { + if (innerType && lookAhead('.')) { + // stop before next nested inner class + return ArgType.object(inclusiveSlice()); + } ch = next(); if (ch == STOP_CHAR) { return null; @@ -185,36 +189,44 @@ public class SignatureParser { if (ch == ';') { String obj; - if (incompleteType) { + if (innerType) { obj = slice().replace('/', '.'); } else { obj = inclusiveSlice(); } return ArgType.object(obj); - } else { - // generic type start ('<') - String obj = slice(); - if (!incompleteType) { - obj += ';'; - } - ArgType[] genArr = consumeGenericArgs(); - consume('>'); + } + // generic type start ('<') + String obj = slice(); + if (!innerType) { + obj += ';'; + } + ArgType[] genArr = consumeGenericArgs(); + consume('>'); - ArgType genericType = ArgType.generic(obj, genArr); - if (lookAhead('.')) { - consume('.'); - next(); - // type parsing not completed, proceed to inner class - ArgType inner = consumeObjectType(true); - if (inner == null) { - throw new JadxRuntimeException("No inner type found: " + debugString()); - } - return ArgType.outerGeneric(genericType, inner); - } else { - consume(';'); - return genericType; + ArgType genericType = ArgType.generic(obj, genArr); + if (!lookAhead('.')) { + consume(';'); + return genericType; + } + consume('.'); + next(); + // type parsing not completed, proceed to inner class + ArgType inner = consumeObjectType(true); + if (inner == null) { + throw new JadxRuntimeException("No inner type found: " + debugString()); + } + // for every nested inner type create nested type object + while (lookAhead('.')) { + genericType = ArgType.outerGeneric(genericType, inner); + consume('.'); + next(); + inner = consumeObjectType(true); + if (inner == null) { + throw new JadxRuntimeException("Unexpected inner type found: " + debugString()); } } + return ArgType.outerGeneric(genericType, inner); } private ArgType[] consumeGenericArgs() { 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 c724aa9cc..3aaaed773 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java @@ -21,6 +21,7 @@ import static jadx.core.dex.instructions.args.ArgType.wildcard; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -58,6 +59,16 @@ class SignatureParserTest { assertThat(objectStr, is("a$LinkedHashIterator")); } + @Test + public void testNestedInnerGeneric() { + String signature = "La.I.X;"; + ArgType result = new SignatureParser(signature).consumeType(); + assertThat(result.getObject(), is("a$I$X")); + // nested 'outerGeneric' objects + ArgType obj = generic("La;", genericType("V")); + assertThat(result, equalTo(outerGeneric(outerGeneric(obj, object("I")), object("X")))); + } + @Test public void testWildcards() { checkWildcards("*", wildcard()); diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestFieldFromInnerClass.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestFieldFromInnerClass.java new file mode 100644 index 000000000..9c6051611 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestFieldFromInnerClass.java @@ -0,0 +1,41 @@ +package jadx.tests.integration.deobf; + +import java.util.List; +import java.util.Queue; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestFieldFromInnerClass extends IntegrationTest { + + public static class TestCls { + TestCls.I f; + + public class I { + Queue a; + + Queue.I> b; + + public class X { + List.I.X> c; + } + } + } + + @Test + public void test() { + noDebugInfo(); + enableDeobfuscation(); + + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls) + .code() + .doesNotContain("class I {") + .doesNotContain(".I ") + .doesNotContain(".I.X>"); + } +}