Skip to content

Commit

Permalink
fix: restore missing type parameter declarations (#1800)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Mar 17, 2023
1 parent 912c431 commit 950fbba
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 8 deletions.
8 changes: 2 additions & 6 deletions jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

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;
Expand Down Expand Up @@ -56,8 +54,6 @@
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;

public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);

private final RootNode root;
private final IClassData clsData;

Expand Down Expand Up @@ -174,10 +170,10 @@ private ArgType checkSuperType(IClassData cls) {
return ArgType.object(superType);
}

public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
public void updateGenericClsData(List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
this.generics = generics;
this.superClass = superClass;
this.interfaces = interfaces;
this.generics = generics;
}

private static void processAttributes(ClassNode cls) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

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;
Expand All @@ -18,7 +22,6 @@
import jadx.core.utils.exceptions.JadxException;

public class SignatureProcessor extends AbstractVisitor {

private RootNode root;

@Override
Expand Down Expand Up @@ -55,12 +58,54 @@ private void parseClassSignature(ClassNode cls) {
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<ArgType> fixTypeParamDeclarations(ClassNode cls,
List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) {
return generics;
}
Set<String> typeParams = new HashSet<>();
superClass.visitTypes(t -> addGenericType(typeParams, t));
interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t)));
if (typeParams.isEmpty()) {
return generics;
}
List<ArgType> 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<ArgType> 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<String> 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));
Expand Down
5 changes: 5 additions & 0 deletions jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ public List<ClassNode> decompileFiles(List<File> files) {
return sortedClsNodes;
}

@NotNull
public ClassNode searchTestCls(List<ClassNode> list, String shortClsName) {
return searchCls(list, getTestPkg() + '.' + shortClsName);
}

@NotNull
public ClassNode searchCls(List<ClassNode> list, String clsName) {
for (ClassNode cls : list) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <T> Map<T, T> test(List<? extends T> list) {
return toMap(list, Lambda.INSTANCE);
}

/**
* Smali test missing 'T' definition in 'Lambda<T>'
*/
private static class Lambda<T> implements Function<T, T> {
public static final Lambda INSTANCE = new Lambda();

@Override
public T apply(T t) {
return t;
}
}

private static <T> Map<T, T> toMap(List<? extends T> list, Function<T, T> 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<ClassNode> classNodes = loadFromSmaliFiles();
assertThat(searchTestCls(classNodes, "Lambda"))
.code()
.containsOne("class Lambda<T> implements Function<T, T> {");
assertThat(searchTestCls(classNodes, "TestCls"))
.code()
.containsOne("Lambda.INSTANCE");
}

@NotYetImplemented("Inline lambda by instance field")
@Test
public void testSmali() {
List<ClassNode> classNodes = loadFromSmaliFiles();
assertThat(classNodes)
.describedAs("Expect lambda to be inlined")
.hasSize(1);
assertThat(searchTestCls(classNodes, "TestCls"))
.code()
.doesNotContain("Lambda.INSTANCE");
}
}
40 changes: 40 additions & 0 deletions jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali
Original file line number Diff line number Diff line change
@@ -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",
"<TT;TT;>;"
}
.end annotation

.field public static final INSTANCE:Linline/Lambda;

.method static constructor <clinit>()V
.registers 1
new-instance v0, Linline/Lambda;
invoke-direct {v0}, Linline/Lambda;-><init>()V
sput-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
return-void
.end method

.method private constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()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
56 changes: 56 additions & 0 deletions jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali
Original file line number Diff line number Diff line change
@@ -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 = {
"<T:",
"Ljava/lang/Object;",
">(",
"Ljava/util/List",
"<+TT;>;)",
"Ljava/util/Map",
"<TT;TT;>;"
}
.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 = {
"<T:",
"Ljava/lang/Object;",
">(",
"Ljava/util/List",
"<+TT;>;",
"Ljava/util/function/Function",
"<TT;TT;>;)",
"Ljava/util/Map",
"<TT;TT;>;"
}
.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

0 comments on commit 950fbba

Please sign in to comment.