fix: support full class name in inner generic types (#1340)

This commit is contained in:
Skylot
2022-01-22 18:03:55 +00:00
parent 5eb7cc40ed
commit 19827fca20
12 changed files with 252 additions and 24 deletions
@@ -526,12 +526,38 @@ public class ClassGen {
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
addInnerType(code, type);
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
String fullNameObj;
if (innerType.getObject().contains(".")) {
fullNameObj = innerType.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, innerType);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
@@ -556,15 +582,6 @@ public class ClassGen {
}
}
private void useClassShortName(ICodeWriter 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(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
@@ -246,13 +246,13 @@ public final class ClassInfo implements Comparable<ClassInfo> {
}
public void notInner(RootNode root) {
this.parentClass = null;
splitAndApplyNames(root, type, false);
this.parentClass = null;
}
public void convertToInner(ClassNode parent) {
this.parentClass = parent.getClassInfo();
splitAndApplyNames(parent.root(), type, true);
this.parentClass = parent.getClassInfo();
}
public void updateNames(RootNode root) {
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@@ -197,6 +196,8 @@ public class SignatureParser {
String obj = slice();
if (!innerType) {
obj += ';';
} else {
obj = obj.replace('/', '.');
}
List<ArgType> typeVars = consumeGenericArgs();
consume('>');
@@ -227,7 +228,7 @@ public class SignatureParser {
}
private List<ArgType> consumeGenericArgs() {
List<ArgType> list = new LinkedList<>();
List<ArgType> list = new ArrayList<>();
ArgType type;
do {
if (lookAhead('*')) {
@@ -63,7 +63,7 @@ public class TypeUtils {
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) {
expandTypeVar(cls, type, cls.getGenericTypeParameters());
expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
}
return type;
}
@@ -115,11 +115,18 @@ public class TypeUtils {
return varsAttr.getTypeVars();
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
ClassNode declCls = mth.getParentClass();
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
if (cls.isInner()) {
Set<ArgType> typeVars = new HashSet<>(cls.getGenericTypeParameters());
cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
return typeVars;
}
return cls.getGenericTypeParameters();
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
Set<ArgType> typeVars = new HashSet<>();
typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass()));
typeVars.addAll(mth.getTypeParameters());
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
}
@@ -80,11 +80,15 @@ public class SignatureProcessor extends AbstractVisitor {
}
ClassNode cls = field.getParentClass();
try {
ArgType gType = sp.consumeType();
if (gType == null) {
ArgType signatureType = sp.consumeType();
if (signatureType == null) {
return;
}
ArgType type = root.getTypeUtils().expandTypeVariables(cls, gType);
if (!validateInnerType(signatureType)) {
field.addWarnComment("Incorrect inner types in field signature: " + sp.getSignature());
return;
}
ArgType type = root.getTypeUtils().expandTypeVariables(cls, signatureType);
if (!validateParsedType(type, field.getType())) {
cls.addWarnComment("Incorrect field signature: " + sp.getSignature());
return;
@@ -105,6 +109,11 @@ public class SignatureProcessor extends AbstractVisitor {
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
ArgType parsedRetType = sp.consumeType();
if (!validateInnerType(parsedRetType) || !validateInnerType(parsedArgTypes)) {
mth.addWarnComment("Incorrect inner types in method signature: " + sp.getSignature());
return;
}
mth.updateTypeParameters(typeParameters); // apply before expand args
TypeUtils typeUtils = root.getTypeUtils();
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
@@ -172,4 +181,54 @@ public class SignatureProcessor extends AbstractVisitor {
TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType);
return result != TypeCompareEnum.CONFLICT;
}
private boolean validateInnerType(List<ArgType> types) {
for (ArgType type : types) {
if (!validateInnerType(type)) {
return false;
}
}
return true;
}
private boolean validateInnerType(ArgType type) {
ArgType innerType = type.getInnerType();
if (innerType == null) {
return true;
}
// check in outer type has inner type as inner class
ArgType outerType = type.getOuterType();
ClassNode outerCls = root.resolveClass(outerType);
if (outerCls == null) {
// can't check class not found
return true;
}
String innerObj;
if (innerType.getOuterType() != null) {
innerObj = innerType.getOuterType().getObject();
// "next" inner type will be processed at end of method
} else {
innerObj = innerType.getObject();
}
if (!innerObj.contains(".")) {
// short reference
for (ClassNode innerClass : outerCls.getInnerClasses()) {
if (innerClass.getShortName().equals(innerObj)) {
return true;
}
}
return false;
}
// full name
ClassNode innerCls = root.resolveClass(innerObj);
if (innerCls == null) {
return false;
}
if (!innerCls.getParentClass().equals(outerCls)) {
// not inner => fixing
outerCls.addInnerClass(innerCls);
innerCls.getClassInfo().convertToInner(outerCls);
}
return validateInnerType(innerType);
}
}
@@ -70,6 +70,19 @@ class SignatureParserTest {
assertThat(result, equalTo(outerGeneric(outerGeneric(obj, object("I")), object("X"))));
}
@Test
public void testNestedInnerGeneric2() {
// full name in inner class
String signature = "Lsome/long/pkg/ba<Lsome/pkg/s;>.some/long/pkg/bb<Lsome/pkg/p;Lsome/pkg/n;>;";
ArgType result = new SignatureParser(signature).consumeType();
System.out.println(result);
assertThat(result.getObject(), is("some.long.pkg.ba$some.long.pkg.bb"));
ArgType baseObj = generic("Lsome/long/pkg/ba;", object("Lsome/pkg/s;"));
ArgType innerObj = generic("Lsome/long/pkg/bb;", object("Lsome/pkg/p;"), object("Lsome/pkg/n;"));
ArgType obj = outerGeneric(baseObj, innerObj);
assertThat(result, equalTo(obj));
}
@Test
public void testWildcards() {
checkWildcards("*", wildcard());
@@ -0,0 +1,43 @@
package jadx.tests.integration.types;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.api.CommentsLevel;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestGenericsInFullInnerCls extends SmaliTest {
@Test
public void test() {
getArgs().setCommentsLevel(CommentsLevel.WARN);
List<ClassNode> classNodes = loadFromSmaliFiles();
assertThat(searchCls(classNodes, "types.FieldCls"))
.code()
.containsOne("private ba<n>.bb<n, n> a;");
assertThat(searchCls(classNodes, "types.test.ba"))
.code()
.containsOne("public final class ba<S> {")
.containsOne("public final class bb<T, V extends n> {")
.containsOne("private ba<S> b;")
.containsOne("private ba<S>.bb<T, V>.bc<T, V> c;")
.containsOne("public final class bc<T, V extends n> {")
.containsOne("private ba<S> a;");
}
@Test
public void testWithDeobf() {
enableDeobfuscation();
args.setDeobfuscationMinLength(100); // rename everything
getArgs().setCommentsLevel(CommentsLevel.WARN);
loadFromSmaliFiles();
// compilation should pass
}
}
@@ -0,0 +1,15 @@
.class public Ltypes/FieldCls;
.super Ljava/lang/Object;
.field private a:Landroidx/compose/animation/core/bb;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ltypes/test/ba<",
"Ltypes/n;",
">.types/test/bb<",
"Ltypes/n;",
"Ltypes/n;",
">;"
}
.end annotation
.end field
@@ -0,0 +1,11 @@
.class public final Ltypes/test/ba;
.super Ljava/lang/Object;
.annotation system Ldalvik/annotation/Signature;
value = {
"<S:",
"Ljava/lang/Object;",
">",
"Ljava/lang/Object;"
}
.end annotation
@@ -0,0 +1,33 @@
.class public final Ltypes/test/bb;
.super Ljava/lang/Object;
# annotations
.annotation system Ldalvik/annotation/Signature;
value = {
"<T:",
"Ljava/lang/Object;",
"V:",
"Ltypes/n;",
">",
"Ljava/lang/Object;"
}
.end annotation
.field private b:Ltypes/test/ba;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ltypes/test/ba<",
"TS;>;"
}
.end annotation
.end field
.field private c:Landroidx/compose/animation/core/bc;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ltypes/test/ba<",
"TS;>.types/test/bb<TT;TV;>.types/test/bc<TT;TV;>;"
}
.end annotation
.end field
@@ -0,0 +1,27 @@
.class public final Ltypes/test/bc;
.super Ljava/lang/Object;
# annotations
.annotation system Ldalvik/annotation/Signature;
value = {
"<T:",
"Ljava/lang/Object;",
"V:",
"Ltypes/n;",
">",
"Ljava/lang/Object;",
"Ltypes/test/ca<",
"TT;>;"
}
.end annotation
.field private a:Ltypes/test/ba;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ltypes/test/ba<",
"TS;>;"
}
.end annotation
.end field
@@ -0,0 +1,2 @@
.class public Ltypes/n;
.super Ljava/lang/Object;