diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 6a981ddeabacfe0463ba58961937e60f1b4e2eb5..5d413042d8f6eb8a4310943a8a816b0838699201 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -13,8 +13,6 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import jadx.api.DecompilationMode; import jadx.api.ICodeCache; @@ -56,8 +54,6 @@ import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable { - private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); - private final RootNode root; private final IClassData clsData; @@ -174,10 +170,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return ArgType.object(superType); } - public void updateGenericClsData(ArgType superClass, List interfaces, List generics) { + public void updateGenericClsData(List generics, ArgType superClass, List interfaces) { + this.generics = generics; this.superClass = superClass; this.interfaces = interfaces; - this.generics = generics; } private static void processAttributes(ClassNode cls) { 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 6dd492de4df1a9c3aa8253fb5d632dc938eb1bae..3944ee8727a013081c672c41f8d91652c729164d 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 @@ -2,8 +2,12 @@ package jadx.core.dex.visitors; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; @@ -18,7 +22,6 @@ import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxException; public class SignatureProcessor extends AbstractVisitor { - private RootNode root; @Override @@ -55,12 +58,54 @@ public class SignatureProcessor extends AbstractVisitor { break; } } - cls.updateGenericClsData(superClass, interfaces, generics); + generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces); + cls.updateGenericClsData(generics, superClass, interfaces); } catch (Exception e) { cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e); } } + /** + * Add missing type parameters from super type and interfaces to make code compilable + */ + private static List fixTypeParamDeclarations(ClassNode cls, + List generics, ArgType superClass, List interfaces) { + if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) { + return generics; + } + Set typeParams = new HashSet<>(); + superClass.visitTypes(t -> addGenericType(typeParams, t)); + interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t))); + if (typeParams.isEmpty()) { + return generics; + } + List knownTypeParams; + if (cls.isInner()) { + knownTypeParams = new ArrayList<>(generics); + cls.visitParentClasses(p -> knownTypeParams.addAll(p.getGenericTypeParameters())); + } else { + knownTypeParams = generics; + } + for (ArgType declTypeParam : knownTypeParams) { + typeParams.remove(declTypeParam.getObject()); + } + if (typeParams.isEmpty()) { + return generics; + } + cls.addInfoComment("Add missing generic type declarations: " + typeParams); + List fixedGenerics = new ArrayList<>(generics.size() + typeParams.size()); + fixedGenerics.addAll(generics); + typeParams.stream().sorted().map(ArgType::genericType).forEach(fixedGenerics::add); + return fixedGenerics; + } + + private static @Nullable Object addGenericType(Set usedTypeParameters, ArgType t) { + if (t.isGenericType()) { + usedTypeParameters.add(t.getObject()); + } + return null; + } + private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) { if (!candidateType.isObject()) { cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls)); diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index f78a61b032fc73c22ecfa9e527ac6c9efec8ac63..2f3e8760039af0383061b797bfc8c250f6bf9bff 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -224,6 +224,11 @@ public abstract class IntegrationTest extends TestUtils { return sortedClsNodes; } + @NotNull + public ClassNode searchTestCls(List list, String shortClsName) { + return searchCls(list, getTestPkg() + '.' + shortClsName); + } + @NotNull public ClassNode searchCls(List list, String clsName) { for (ClassNode cls : list) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java new file mode 100644 index 0000000000000000000000000000000000000000..fce8482a1a5a75c30e91850f725f38fcb1f64ac0 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java @@ -0,0 +1,72 @@ +package jadx.tests.integration.inline; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import jadx.NotYetImplemented; +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestInstanceLambda extends SmaliTest { + + @SuppressWarnings({ "unchecked", "rawtypes", "SameParameterValue" }) + public static class TestCls { + + public Map test(List list) { + return toMap(list, Lambda.INSTANCE); + } + + /** + * Smali test missing 'T' definition in 'Lambda' + */ + private static class Lambda implements Function { + public static final Lambda INSTANCE = new Lambda(); + + @Override + public T apply(T t) { + return t; + } + } + + private static Map toMap(List list, Function valueMap) { + return list.stream().collect(Collectors.toMap(k -> k, valueMap)); + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code(); + } + + @Test + public void testSmaliDisableInline() { + args.setInlineAnonymousClasses(false); + List classNodes = loadFromSmaliFiles(); + assertThat(searchTestCls(classNodes, "Lambda")) + .code() + .containsOne("class Lambda implements Function {"); + assertThat(searchTestCls(classNodes, "TestCls")) + .code() + .containsOne("Lambda.INSTANCE"); + } + + @NotYetImplemented("Inline lambda by instance field") + @Test + public void testSmali() { + List classNodes = loadFromSmaliFiles(); + assertThat(classNodes) + .describedAs("Expect lambda to be inlined") + .hasSize(1); + assertThat(searchTestCls(classNodes, "TestCls")) + .code() + .doesNotContain("Lambda.INSTANCE"); + } +} diff --git a/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali b/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali new file mode 100644 index 0000000000000000000000000000000000000000..d455437530203cd922c06a904119e38a68c62295 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali @@ -0,0 +1,40 @@ +.class public Linline/Lambda; +.super Ljava/lang/Object; + +.implements Ljava/util/function/Function; + + +.annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/lang/Object;", + "Ljava/util/function/Function", + ";" + } +.end annotation + +.field public static final INSTANCE:Linline/Lambda; + +.method static constructor ()V + .registers 1 + new-instance v0, Linline/Lambda; + invoke-direct {v0}, Linline/Lambda;->()V + sput-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda; + return-void +.end method + +.method private constructor ()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + +.method public final apply(Ljava/lang/Object;)Ljava/lang/Object; + .registers 2 + .annotation system Ldalvik/annotation/Signature; + value = { + "(TT;)TT;" + } + .end annotation + + return-object p1 +.end method diff --git a/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali b/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali new file mode 100644 index 0000000000000000000000000000000000000000..1e6a2899181953cb1c0238bb639ca142b45a25d7 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali @@ -0,0 +1,56 @@ +.class public Linline/TestCls; +.super Ljava/lang/Object; + +.method public test(Ljava/util/List;)Ljava/util/Map; + .registers 3 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Ljava/util/List", + "<+TT;>;)", + "Ljava/util/Map", + ";" + } + .end annotation + + sget-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda; + invoke-static {p1, v0}, Linline/TestCls;->toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map; + move-result-object v0 + return-object v0 +.end method + + +.method private static synthetic lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object; + .registers 1 + return-object p0 +.end method + +.method private static toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map; + .registers 4 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Ljava/util/List", + "<+TT;>;", + "Ljava/util/function/Function", + ";)", + "Ljava/util/Map", + ";" + } + .end annotation + + invoke-interface {p0}, Ljava/util/List;->stream()Ljava/util/stream/Stream; + move-result-object v0 + invoke-custom {}, call_site_0("apply", ()Ljava/util/function/Function;, (Ljava/lang/Object;)Ljava/lang/Object;, invoke-static@Linline/TestCls;->lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/Object;)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + move-result-object v1 + invoke-static {v1, p1}, Ljava/util/stream/Collectors;->toMap(Ljava/util/function/Function;Ljava/util/function/Function;)Ljava/util/stream/Collector; + move-result-object v1 + invoke-interface {v0, v1}, Ljava/util/stream/Stream;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object; + move-result-object v0 + check-cast v0, Ljava/util/Map; + return-object v0 +.end method