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 278fb10e6..95c010313 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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 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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 65ff881dd..be8839a9d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -246,13 +246,13 @@ public final class ClassInfo implements Comparable { } 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) { 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 6f542bf28..eee3d14a1 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 @@ -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 typeVars = consumeGenericArgs(); consume('>'); @@ -227,7 +228,7 @@ public class SignatureParser { } private List consumeGenericArgs() { - List list = new LinkedList<>(); + List list = new ArrayList<>(); ArgType type; do { if (lookAhead('*')) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index 0a6a977ca..d992aee26 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -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 collectKnownTypeVarsAtMethod(MethodNode mth) { - ClassNode declCls = mth.getParentClass(); - Set typeVars = new HashSet<>(declCls.getGenericTypeParameters()); - declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters())); + private static Collection getKnownTypeVarsAtClass(ClassNode cls) { + if (cls.isInner()) { + Set typeVars = new HashSet<>(cls.getGenericTypeParameters()); + cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters())); + return typeVars; + } + return cls.getGenericTypeParameters(); + } + private static Set collectKnownTypeVarsAtMethod(MethodNode mth) { + Set typeVars = new HashSet<>(); + typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass())); typeVars.addAll(mth.getTypeParameters()); return typeVars.isEmpty() ? Collections.emptySet() : typeVars; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java index 810e0b1ab..6dd492de4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java @@ -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 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 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); + } } 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 d8317dea2..82cb5d410 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java @@ -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.some/long/pkg/bb;"; + 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()); diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenericsInFullInnerCls.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenericsInFullInnerCls.java new file mode 100644 index 000000000..97a2232d6 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenericsInFullInnerCls.java @@ -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 classNodes = loadFromSmaliFiles(); + + assertThat(searchCls(classNodes, "types.FieldCls")) + .code() + .containsOne("private ba.bb a;"); + + assertThat(searchCls(classNodes, "types.test.ba")) + .code() + .containsOne("public final class ba {") + .containsOne("public final class bb {") + .containsOne("private ba b;") + .containsOne("private ba.bb.bc c;") + .containsOne("public final class bc {") + .containsOne("private ba a;"); + } + + @Test + public void testWithDeobf() { + enableDeobfuscation(); + args.setDeobfuscationMinLength(100); // rename everything + + getArgs().setCommentsLevel(CommentsLevel.WARN); + loadFromSmaliFiles(); + // compilation should pass + } +} diff --git a/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/FieldCls.smali b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/FieldCls.smali new file mode 100644 index 000000000..fcd5e7c39 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/FieldCls.smali @@ -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 diff --git a/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/ba.smali b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/ba.smali new file mode 100644 index 000000000..f7f632070 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/ba.smali @@ -0,0 +1,11 @@ +.class public final Ltypes/test/ba; +.super Ljava/lang/Object; + +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "Ljava/lang/Object;" + } +.end annotation diff --git a/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bb.smali b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bb.smali new file mode 100644 index 000000000..643555761 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bb.smali @@ -0,0 +1,33 @@ +.class public final Ltypes/test/bb; +.super Ljava/lang/Object; + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "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.types/test/bc;" + } + .end annotation +.end field diff --git a/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bc.smali b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bc.smali new file mode 100644 index 000000000..7fc754d3d --- /dev/null +++ b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/bc.smali @@ -0,0 +1,27 @@ +.class public final Ltypes/test/bc; +.super Ljava/lang/Object; + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "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 diff --git a/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/n.smali b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/n.smali new file mode 100644 index 000000000..6351a7133 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestGenericsInFullInnerCls/n.smali @@ -0,0 +1,2 @@ +.class public Ltypes/n; +.super Ljava/lang/Object;