fix: use recursive objects for nested inner generic classes (#869)

This commit is contained in:
Skylot
2020-07-16 14:14:06 +01:00
parent 631a855bac
commit f5767dd865
5 changed files with 101 additions and 27 deletions
@@ -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) {
@@ -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;
@@ -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() {
@@ -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<TV;>.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());
@@ -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<T> {
TestCls<T>.I f;
public class I {
Queue<T> a;
Queue<TestCls<T>.I> b;
public class X {
List<TestCls<T>.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>");
}
}