fix: use recursive objects for nested inner generic classes (#869)
This commit is contained in:
@@ -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>");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user